==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍
二叉树非递归实现会比较难理解一点,不过只要理解了非递归的,那么递归的就非常好理解了。接下来进行图文详解。
C代码下载
C++代码下载
java代码下载
( 备用地址下载)
导航
1.创建二叉树
2.前序遍历二叉树
3.中序遍历二叉树
4.后序遍历二叉树
5.层次遍历二叉树
6.计算二叉树深度
7.计算二叉树双分支节点数
8.计算二叉树单分支节点数
9.计算二叉树叶子节点数
10.添加节点
11.查找二叉树中的节点
注:有一些重复的代码且多的就不重复贴出来了,需要的可以点上面的链接去下载。
一、创建二叉树
按照前序遍历来创建,给定一个串,其规则是空格代表空节点
例如给定串:ABC D EF G ;
创建步骤如下:
1.C代码
/*
* function 创建二叉树(前序创建)
* param char* s 根据给定字符串创建
* return 返回二叉树的根节点
*/
PBTree CreateBTree(char* s)
{
if (!s || s[0] == '\0') //如果s为空 则树也为空
return NULL;
Stack stack = CreateStack(); //创建一个栈
int i = 0;
// 1.先创建根节点
PBTree root = CreateNode(s[i++]);
PBTree btree = root; //用一个临时的指针代替root,因为最后要返回root指针,所以它不能改变
Push(&stack,root); //将根节点压入栈中
int len = _csize(s);
while (i < len)
{
// 2.如果当前读到的字符不为空,并且当前节点的左孩子不存在,则创建它
if (s[i] != ' ' && btree->bLCExits == false)
{
btree->left = CreateNode(s[i]); //创建左孩子
btree->bLCExits = true; //左孩子存在
btree = btree->left;
Push(&stack, btree); //入栈
++i;
}
// 3.如果当前读到的字符不为空,并且当前节点的右孩子不存在,则创建它
else if (s[i] != ' ' && btree->bRCExits == false)
{
btree->right = CreateNode(s[i]);//创建右孩子
btree->bRCExits = true; //右孩子存在
btree = btree->right;
Push(&stack, btree); //入栈
++i;
}
// 4.如果当前读到的字符为空,并且当前节点的左孩子不存在,则将当前节点的左孩子置为存在(空也算孩子)
else if (s[i] == ' ' && btree->bLCExits == false) //空节点
btree->bLCExits = true, ++i; //左孩子存在
// 5.如果当前读到的字符为空,并且当前节点的右孩子不存在,则将当前节点的右孩子置为存在(空也算孩子)
else if (s[i] == ' ' && btree->bRCExits == false)
btree->bRCExits = true, ++i; //右孩子存在
// 6.回溯回去,当该节点的度为2的时候(就是左右孩子都存在的情况)
if (btree->bLCExits && btree->bRCExits)
Pop(&stack),btree = Top(&stack);
}
DestroyStack(&stack); //最后销毁栈
return root;
}
2.C++代码
/*
* function 创建二叉树(前序创建)
* param char* s 根据给定串创建
* return 无
*/
template
void NonRecursionBTree::Create(string s)
{
if (s.empty()) //如果s为空 则树也为空
return ;
stack st; //创建一个栈
int i = 0;
// 1.先创建根节点
m_root = new Node(s[i++]);
Node* btree = m_root; //用一个临时的指针代替root,因为最后要返回root指针,所以它不能改变
st.push(btree); //将根节点压入栈中
int len = s.size();
while (i < len)
{
// 2.如果当前读到的字符不为空,并且当前节点的左孩子不存在,则创建它
if (s[i] != ' ' && btree->bLCExits == false)
{
btree->left = new Node(s[i]); //创建左孩子
btree->bLCExits = true; //左孩子存在
btree = btree->left;
st.push(btree); //入栈
++i;
}
// 3.如果当前读到的字符不为空,并且当前节点的右孩子不存在,则创建它
else if (s[i] != ' ' && btree->bRCExits == false)
{
btree->right = new Node(s[i]); //创建右孩子
btree->bRCExits = true; //右孩子存在
btree = btree->right;
st.push(btree); //入栈
++i;
}
// 4.如果当前读到的字符为空,并且当前节点的左孩子不存在,则将当前节点的左孩子置为存在(空也算孩子)
else if (s[i] == ' ' && btree->bLCExits == false) //空节点
btree->bLCExits = true, ++i; //左孩子存在
// 5.如果当前读到的字符为空,并且当前节点的右孩子不存在,则将当前节点的右孩子置为存在(空也算孩子)
else if (s[i] == ' ' && btree->bRCExits == false)
btree->bRCExits = true, ++i; //右孩子存在
// 6.回溯回去,当该节点的度为2的时候(就是左右孩子都存在的情况)
if (btree->bLCExits && btree->bRCExits)
st.pop(), btree = st.top();
}
while (!st.empty()) //最后销毁栈
st.pop();
}
3.java代码
/*
* function 创建二叉树(前序创建)
* param String s 根据给定串创建
* return 无
*/
public void create(String s) {
if (s.isEmpty()) //如果s为空 则树也为空
return ;
Stack st = new Stack(); //创建一个栈
int i = 0;
// 1.先创建根节点
root = new Node(s.charAt(i++));
Node btree = root; //用一个临时的指针代替root,因为最后要返回root指针,所以它不能改变
st.push(btree); //将根节点压入栈中
int len = s.length();
while (i < len) {
// 2.如果当前读到的字符不为空,并且当前节点的左孩子不存在,则创建它
if (s.charAt(i) != ' ' && btree.bLCExits == false) {
btree.left = new Node(s.charAt(i)); //创建左孩子
btree.bLCExits = true; //左孩子存在
btree = btree.left;
st.push(btree); //入栈
++i;
}
// 3.如果当前读到的字符不为空,并且当前节点的右孩子不存在,则创建它
else if (s.charAt(i) != ' ' && btree.bRCExits == false) {
btree.right = new Node(s.charAt(i)); //创建右孩子
btree.bRCExits = true; //右孩子存在
btree = btree.right;
st.push(btree); //入栈
++i;
}
// 4.如果当前读到的字符为空,并且当前节点的左孩子不存在,则将当前节点的左孩子置为存在(空也算孩子)
else if (s.charAt(i) == ' ' && btree.bLCExits == false) {//空节点
btree.bLCExits = true;
++i; //左孩子存在
}
// 5.如果当前读到的字符为空,并且当前节点的右孩子不存在,则将当前节点的右孩子置为存在(空也算孩子)
else if (s.charAt(i) == ' ' && btree.bRCExits == false) {
btree.bRCExits = true;
++i; //右孩子存在
}
// 6.回溯回去,当该节点的度为2的时候(就是左右孩子都存在的情况)
if (btree.bLCExits && btree.bRCExits) {
st.pop();
btree = st.peek();
}
}
st.clear(); //最后销毁栈
}
1).为什么要用两个标志位bLCExits和bRCExits?
这样子比较容易找到回溯点,就是当左右两个孩子都存在了就回溯,不能根据是否为空来判断是否回溯。
二、前序遍历二叉树
参考出处,点击跳转
遍历顺序是:根节点 -> 左节点 -> 右节点
然后一个二叉树又可以分为很多子树,每一颗子树都会有根、左、右节点。
/*
* function 前序遍历
* param PBTree root
* return 无
*/
void PreOrder(PBTree root)
{
Stack stack = CreateStack(); //创建一个栈
PBTree btree = root; //创建临时指针指向root
// 1. 若当前节点不为空,或者栈中元素不为空(相当于还没有遍历完所有节点)
while (btree || !StackIsEmpty(&stack))
{
// 2. 先遍历左子树,一直到左子树为空为止。
while (btree)
{
printf("%c", btree->data);
Push(&stack,btree);
btree = btree->left;
}
// 3.若栈中还有元素,则将当前btree赋值为它的右子树,然后再重复 1~2步骤
if (!StackIsEmpty(&stack))
{
btree = Top(&stack);
Pop(&stack);
btree = btree->right;
}
}
printf("\n");
}
三、中序遍历二叉树
遍历顺序是:左节点 -> 根节点 -> 右节点
/*
* function 中序遍历
* param PBTree root
* return 无
*/
void InOrder(PBTree root)
{
Stack stack = CreateStack(); //创建一个栈
PBTree btree = root; //创建临时指针指向root
// 1. 若当前节点不为空,或者栈中元素不为空(相当于还没有遍历完所有节点)
while (btree || !StackIsEmpty(&stack))
{
// 2. 先遍历左子树,一直到左子树为空为止。
while (btree)
{
Push(&stack, btree);
btree = btree->left;
}
// 3.若栈中还有元素,则将当前btree赋值为它的右子树,然后再重复 1~2步骤
if (!StackIsEmpty(&stack))
{
btree = Top(&stack);
printf("%c", btree->data); //遍历完左子树后输出它们的根节点
Pop(&stack);
btree = btree->right;
}
}
printf("\n");
}
四、后序遍历二叉树
遍历顺序:左节点 -> 右节点 -> 根节点
/*
* function 后序遍历
* param PBTree root
* return 无
*/
void PostOrder(PBTree root)
{
Stack stack = CreateStack(); //创建一个栈
PBTree cur; //当前结点
PBTree pre = NULL; //前一次访问的结点
Push(&stack,root); //先将根节点入栈
while (!StackIsEmpty(&stack))
{
cur = Top(&stack); //这里的cur就像是每颗子树的根节点,然后重复这些步骤
if ((!cur->left && !cur->right) ||
(pre && (pre == cur->left || pre == cur->right)))
{
printf("%c", cur->data); //如果当前结点没有孩子结点或者孩子节点都已被访问过
Pop(&stack);
pre = cur;
}
else
{
if (cur->right != NULL) //这里先将右孩子入栈,这样出栈的时候,左孩子就先出栈
Push(&stack,cur->right);
if (cur->left != NULL)
Push(&stack,cur->left);
}
}
printf("\n");
}
五、层次遍历二叉树
层次遍历就简单了,这个要用到队列
步骤为:
1.将根节点入队
2.将当前节点置为队头节点 (cur = front)
3.出队
4.访问当前节点
5.如果当前节点的左孩子不为空,左孩子入队
6.如果当前节点的右孩子不为空,有孩子入队
7.重复2~6步骤即可
这个就相当于访问上一层的节点的时候,将下一层的结点依次入队以待访问
/*
* function 层次遍历
* param PBTree root
* return 无
*/
void translevel(PBTree root)
{
if (!root) return;
Queue queue = CreateQueue(); //创建一个队列
PBTree cur = root; //当前节点
QPush(&queue, root); //先将根节点加入队列中
while (!QueueIsEmpty(&queue)) //当队列中没有元素时,遍历完成
{
cur = Front(&queue); //获取当前队头元素
QPop(&queue); //遍历过后就出队
printf("%c", cur->data); //先输出该子树的根节点
if (cur->left) //如果左孩子不为空,则入队等待遍历
QPush(&queue, cur->left);
if (cur->right) //如果右孩子不为空,则入队等待遍历
QPush(&queue, cur->right);
}
printf("\n");
}
六、计算二叉树深度
利用层次遍历来求,遍历的最大层数即深度
/*
* function 计算二叉树深度
* param PBTree root
* return 无
*/
int BTreeDepth(PBTree root)
{
if (!root) return 0;
Queue queue = CreateQueue(); //创建一个队列
PBTree cur = root; //当前节点
int iDepth = 0;
QPush(&queue, root); //先将根节点加入队列中
while (!QueueIsEmpty(&queue)) //当队列中没有元素时,遍历完成
{
++iDepth; //外层循环控制层数
int curLevelNodes = queue.count; //当前层数的节点数
int temp = 0; //临时记录已经遍历的节点数
while (temp++ < curLevelNodes) //当遍历完当前层数后,退出内层循环,继续遍历下一层
{
cur = Front(&queue); //获取当前队头元素
QPop(&queue); //遍历过后就出队
if (cur->left) //如果左孩子不为空,则入队等待遍历
QPush(&queue, cur->left);
if (cur->right) //如果右孩子不为空,则入队等待遍历
QPush(&queue, cur->right);
}
}
return iDepth;
}
七、计算二叉树双分支节点数
利用前序遍历来求
/*
* function 计算二叉树双分支节点数
* param PBTree root
* return 无
*/
int GetN2(PBTree root)
{
Stack stack = CreateStack(); //创建一个栈
PBTree btree = root; //创建临时指针指向root
int count = 0;
//利用前序遍历来访问所有的节点
while (btree || !StackIsEmpty(&stack))
{
while (btree)
{
Push(&stack, btree);
btree = btree->left;
}
if (!StackIsEmpty(&stack))
{
btree = Top(&stack);
if (btree) //再这里能保证所有的节点都能被遍历到
if (btree->left && btree->right) //当该节点有两个分支的时候+1
++count;
Pop(&stack);
btree = btree->right;
}
}
return count;
}
八、计算二叉树单分支节点数
和计算双分支节点的方法一样,只需要把判断语句改一下即可
if (btree) //再这里能保证所有的节点都能被遍历到
if ((btree->left && !btree->right) || (!btree->left && btree->right)) //当该节点仅且只有一个分支的时候+1
++count;
九、计算二叉树叶子节点数
这个就简单了,有一个公式: n0 = n2 + 1
/*
* function 计算二叉树终端节点数
* param PBTree root
* return 无
*/
int GetN0(PBTree root)
{
return GetN2(root) + 1; //计算公式n0 = n2 + 1;
}
十、添加节点
可以用前序、中序、后序、层次遍历的方法来添加,前三个的缺点很明显,最后添加后可能会退化成一个长长的单链表。所以这里采用层次遍历的方法添加,一层层扫描,遇到空节点就添加在它那里。
/*
* function 添加节点值(添加的位置是不确定的)
* param PBTree root
* param char ch
* return 无
*/
void AddValue(PBTree root, char ch)
{
//采用层次遍历的办法,一层层扫描,若有空的地方,则添加到该地方
if (!root)
{
root = CreateNode(ch);
return;
}
Queue queue = CreateQueue(); //创建一个队列
PBTree cur = root; //当前节点
QPush(&queue, root); //先将根节点加入队列中
while (!QueueIsEmpty(&queue)) //当队列中没有元素时,遍历完成
{
cur = Front(&queue); //获取当前队头元素
QPop(&queue); //遍历过后就出队
if (cur->left) //如果左孩子不为空,则入队等待遍历
QPush(&queue, cur->left);
else //否则就添加值,然后退出
{
cur->left = CreateNode(ch);
break;
}
if (cur->right) //如果右孩子不为空,则入队等待遍历
QPush(&queue, cur->right);
else //否则就添加值,然后退出
{
cur->right = CreateNode(ch);//否则就添加值,然后退出
break;
}
}
}
十一、查找二叉树中的节点
用层次遍历的方法查找
/*
* function 查找值
* param PBTree root
* param char ch
* return 成功返回true,否则返回false
*/
bool Search(PBTree root, char ch)
{
//利用层次遍历来查找
if (!root) return false;
Queue queue = CreateQueue(); //创建一个队列
PBTree cur = root; //当前节点
QPush(&queue, root); //先将根节点加入队列中
while (!QueueIsEmpty(&queue)) //当队列中没有元素时,遍历完成
{
cur = Front(&queue); //获取当前队头元素
if (cur->data == ch)
return true;
QPop(&queue); //遍历过后就出队
if (cur->left) //如果左孩子不为空,则入队等待遍历
QPush(&queue, cur->left);
if (cur->right) //如果右孩子不为空,则入队等待遍历
QPush(&queue, cur->right);
}
return false;
}