本文章是关于二叉树的非递归实现先序遍历、中序遍历、后序遍历、层序遍历以及求二叉树的镜像、判定一棵二叉树是否为完全二叉树、利用先序遍历和中序遍历的结果还原二叉树的操作。
1. 非递归实现先序遍历
关于非递归实现先序遍历,需要用到的是栈,充分利用栈的先进后出的规则。举例来说明怎么去利用栈实现非递归的先序遍历:
总结思路:1. 将根节点入栈
2.进入循环:(1)取栈顶元素 访问 出栈
(2)将取出的栈顶元素的右子树入栈
(3)将取出的栈顶元素的左子树入栈
代码实现如下:
void TreePreOrderByLoop(TreeNode* root)
{
//判断是否为空树
if(root==NULL)
{
return;
}
//0.在利用栈实现先序遍历前,先创建一个栈
SeqStack stack;
SeqStackInit(&stack);
//1.将二叉树的根节点入栈
SeqStackPush(&stack,root);
//2.循环开始,当栈为空时,循环结束 => while循环的结束条件为取栈顶元素失败时
TreeNode* cur=NULL;
while(SeqStackTop(&stack,&cur))
{
//(1)取栈顶元素后 访问该节点 出栈
printf("%c ",cur->data);
SeqStackPop(&stack);
//(2)将出栈的节点的右子树入栈
if(cur->rchild!=NULL){
SeqStackPush(&stack,cur->rchild);
}
//(3)将出栈的节点的左子树入栈
if(cur->lchild!=NULL)
{
SeqStackPush(&stack,cur->lchild);
}
}
}
2. 非递归实现中序遍历
关于非递归实现中序遍历,需要用到的是栈,充分利用栈的先进后出的规则。举例来说明怎么去利用栈实现非递归的中序遍历:
总结思路:1.定义一个栈以及定义一个指针cur指向根节点并将根节点入栈
2.循环开始:(1)循环判断cur的左子树是否为空?若非空,将其入栈
(2)若为空,取栈顶元素 访问 出栈
(3)让cur指向取出的栈顶元素的右子树,循环判空!
代码实现如下:
void TreeInOrderByLoop(TreeNode* root)
{
//判断空树
if(root==NULL)
{
return;
}
//0.定义一个栈以及定义一个用于遍历二叉树的指针cur
SeqStack stack;
SeqStackInit(&stack);
TreeNode* cur=NULL;
//1.将cur指向根节点,并将cur入栈
cur=root;
SeqStackPush(&stack,cur);
cur=cur->lchild;
//在2 3 4步上需要一个大循环,循环遍历二叉树
while(1)
{
//2.循环判定cur的左子树是否为空?若不为空,cur入栈并将cur指向cur->lchild
while(cur!=NULL)
{
SeqStackPush(&stack,cur);
cur=cur->lchild;
}
//3.若为空,取栈顶元素 访问 出栈
//这里当取栈顶元素失败时,相当于遍历二叉树结束,跳出循环
TreeNode* top;
int ret=SeqStackTop(&stack,&top);
if(ret==0)
{
return;
}
printf("%c ",top->data);
SeqStackPop(&stack);
//4.让cur指向取出的栈顶元素的右子树,循环判断cur的左子树是否为空...
cur=top; 指向取出的栈顶元素的右子树
cur=cur->rchild;
}
}
3. 非递归实现后序遍历
关于非递归实现后序遍历,需要用到的是栈,充分利用栈的先进后出的规则。举例来说明怎么去利用栈实现非递归的后序遍历:
总结思路:1.定义一个指针用于遍历二叉树,先将cur指向根节点
2.进入循环:(1)先循环判定cur是否为空?若非空,将cur入栈并将cur指向cur->lchild
(2)若为空,取栈顶元素并判断是否可以访问
(3)若可以访问栈顶元素,则进行打印栈顶元素节点的data并出栈
(4)若不可以访问栈顶元素,则将cur指向该栈顶元素的右子树
代码实现如下:
void TreePostOrderByLoop(TreeNode* root)
{
//判断空树
if(root==NULL)
{
return;
}
//1.定义一个栈以及定义一个指针cur指向根节点root
SeqStack stack;
SeqStackInit(&stack);
TreeNode* cur=root;
//由于需要判定栈顶元素是否可以被访问,故需要另一个指针,指向前一个被访问的节点
TreeNode* pre=NULL;
//2.在一个大循环里通过cur遍历二叉树
while(1)
{
//(1)循环判定cur是否为空?若不为空,将cur入栈并将cur指向cur->lchild
while(cur!=NULL)
{
SeqStackPush(&stack,cur);
cur=cur->lchild;
}
//(2)若为空,取栈顶元素但并不立即访问
TreeNode* top;
int ret=SeqStackTop(&stack,&top);
//取栈顶元素失败时,相当于cur遍历二叉树结束,直接return
if(ret==0)
{
return;
}
//(3)判定栈顶元素是否可以访问
if(top->rchild==NULL||top->rchild==pre)
{
//若栈顶元素无右子树或右子树被访问过,则访问栈顶元素
printf("%c ",top->data);
//出栈
SeqStackPop(&stack);
//将pre指向刚访问过的节点
pre=top;
}
//当前栈顶元素不可以访问时,继续循环判空cur的右子树
else
{
cur=top->rchild;
}
}
}
4. 非递归的层序遍历
关于非递归的层序遍历,需要用到的不再是栈而是队列,充分利用队列的先进先出的规则。举例来说明怎么去利用队列实现非递归的层序遍历:
总结思路:1.定义一个队列并将根节点入队列
2.进入循环:(1)取队首元素 访问 出队列
(2)将(1)中取出的队首元素的左右子树依次入队列
代码实现如下:
void TreeLevelOrderByLoopEx(TreeNode* root)
{
//判断空树
if(root==NULL)
{
return;
}
//1.定义一个队列并将根节点root入队列
SeqQueue queue;
SeqQueueInit(&queue);
SeqQueuePush(&queue,root);
//2.进入循环
while(1)
{
//(1)取队首元素 访问 出队列
TreeNode* front;
int ret=SeqQueueFront(&queue,&front);
//当取队首元素失败时,表示二叉树遍历结束,直接return
if(ret==0)
{
return;
}
printf("%c ",front->data);
SeqQueuePop(&queue);
//(2)将(1)中的队首元素的左右子树依次入队列
if(front->lchild!=NULL)
{
SeqQueuePush(&queue,front->lchild);
}
if(front->rchild!=NULL)
{
SeqQueuePush(&queue,front->rchild);
}
}
}
5. 求二叉树的镜像
求二叉树的镜像,即相当于求二叉树的翻转,即将二叉树每一个节点的左右子树交换,但要做到不重复不遗漏!先画图说明二叉树的镜像问题(利用先序遍历的方式遍历二叉树):
总结思路:利用先序遍历的方式遍历二叉树,即先访问根节点 再访问左子树 最后访问右子树,此时的访问操作不再是打印而是交换其左右子树。
(1)递归实现求二叉树的镜像代码如下:
//整体思路:利用先序遍历的方式将左右子树进行交换
//1.从根节点开始访问,访问该节点的操作为交换该节点的左右子树
//2.递归访问当前节点的左子树
//3.递归访问当前节点的右子树
void Swap(TreeNode** left,TreeNode** right)
{
TreeNode* tmp=*left;
*left=*right;
*right=tmp;
}
void TreeMirror(TreeNode* root)
{
//判断空树
if(root==NULL)
{
return;
}
//1.从根节点开始访问,访问该节点的操作为交换该节点的左右子树
Swap(&root->lchild,&root->rchild);
//2.递归访问当前节点的左子树
TreeMirror(root->lchild);
//3.递归访问当前节点的右子树
TreeMirror(root->rchild);
}
(2)非递归实现求二叉树的镜像代码如下:
//整体思路:利用层序遍历的方式去访问二叉树的节点
//1.定义一个队列用于层序遍历,这是利用了队列的先进先出的规则
//2.将根节点root入队列
//3.循环开始遍历二叉树:(1)取队首元素 访问 出队列 ====>此时的访问不再是打印节点数据元素而是交换节点的左右子树
// (2)将(1)中的队首元素的左子树入队列
// (3)将(1)中的队首元素的右子树入队列
void TreeMirrorByLoop(TreeNode* root)
{
//判断空树
if(root==NULL)
{
return;
}
//1.定义一个队列
SeqQueue queue;
SeqQueueInit(&queue);
//2.将根节点入队列
SeqQueuePush(&queue,root);
//3.进入循环
while(1)
{
//(1)取队首元素 访问(即交换左右子树) 出队列
TreeNode* front;
int ret=SeqQueueFront(&queue,&front);
//当取队首元素失败时,表示二叉树遍历结束,直接return
if(ret==0)
{
return;
}
//访问,即交换队首元素的左右子树
Swap(&front->lchild,&front->rchild);
//出队列
SeqQueuePop(&queue);
//(2)将(1)中的队首元素的左子树入队列
if(front->lchild!=NULL)
{
SeqQueuePush(&queue,front->lchild);
}
//(3)将(1)中的队首元素的右子树入队列
if(front->rchild!=NULL)
{
SeqQueuePush(&queue,front->rchild);
}
}
}
6. 判定二叉树是否为完全二叉树
画图演示哪些是完全二叉树?哪些不是完全二叉树?
先总结一棵二叉树是完全二叉树时,需满足的条件:
1.任何一个节点同时具备左右子树,这棵树为完全二叉树
2.一旦发现某个节点不是同时具备子树时:(1)若当前节点只有右子树,一定不是完全二叉树
(2)若当前节点只有左子树,进入第二阶段的判断
(3)若当前节点没有子树时,进入第二阶段的判断
3.第二阶段判断:任何一个节点都没有子树时,为完全二叉树!
思路总结:利用层序遍历的方式去访问二叉树的节点
1.定义一个队列并将根节点root入队列
2.进入循环:(1)取队首元素并出队列
(2)第一阶段:判定(1)中的队首元素是否同时具有左右子树?若不是同时具有,则分三种情况:
(1)若队首元素只有右子树,一定不是完全二叉树,直接return 0
(2)若队首元素只有左子树,进入第二阶段的判断
(3)若队首元素没有子树,继续循环
(3)第二阶段:队首元素的左子树没有子树时,继续循环;否则,不是完全二叉树,直接return 0
代码实现如下:
int IsCompleteTree(TreeNode* root)
{
//判断空树
if(root==NULL)
{
return 0;
}
//1.定义一个队列并将根节点root入队列
SeqQueue queue;
SeqQueueInit(&queue);
SeqQueuePush(&queue,root);
//2.进入循环,当取队首元素失败时表示二叉树遍历结束
TreeNode* front;
while(SeqQueueFront(&queue,&front))
{
//(1)取队首元素后出队列
SeqQueuePop(&queue);
//为了区分两个阶段,故定义一个标志位标识应执行的是第一阶段还是第二阶段
int flag=0;
if(flag==0)
{
//(2)判断取出队首元素是否同时具备左右子树
if(front->lchild!=NULL&&front->rchild!=NULL)
{
//(1)当同时有左右子树时,分别入队列并继续循环遍历二叉树
SeqQueuePush(&queue,front->lchild);
SeqQueuePush(&queue,front->rchild);
}
else if(front->lchild==NULL&&front->rchild!=NULL)
{
//(2)左子树为空,右子树非空时,一定不是完全二叉树
return 0;
}
else if(front->lchild!=NULL&&front->rchild==NULL)
{
//(3)左子树非空,右子树为空时,进入第二阶段的判断
flag=1;
SeqQueuePush(&queue,front->lchild);
}
else
{
flag=1;
}
}
if(flag==1)
{
if(front->lchild==NULL&&front->rchild==NULL)
{
;
}
else
{
return 0;
}
}
}
return 1;
}
7. 利用先序遍历和中序遍历还原二叉树
//整体思路:利用递归的方式,先序遍历中的每一个节点在中序遍历中都能找到它的左右子树的区间
TreeNode* TreeRebuild(TreeNodeType pre_order[],TreeNodeType in_order[],int size)
{
//定义一个变量,用于遍历先序遍历的结果
size_t pre_order_index=0;
//定义两个变量,用于当前节点的左右子树在中序遍历结果的区间
size_t in_order_left=0;
size_t in_order_right=size;
//以上规定,中序遍历结果在[in_order_left,in_order_right)区间上
return _TreeRebuild(pre_order,size,&pre_order_index,in_order,in_order_left,in_order_right);
}
TreeNode* _TreeRebuild(TreeNodeType pre_order[],size_t pre_order_size,size_t* pre_order_index,
TreeNodeType in_order[],size_t in_order_left,size_t in_order_right)
{
//判断中序遍历的区间是否有效?
if(in_order_left>=in_order_right)
{
//该区间为无效区间,表示当前子树的中序遍历为空,说明当前子树为空树
return NULL;
}
//判断pre_order_index是否非法输入
if(pre_order_index==NULL)
{
return NULL;
}
//判断pre_order_index是否将先序遍历结果遍历完了?
if(*pre_order_index>=pre_order_size)
{
return NULL;
}
//创建当前节点,new_node相当于当前子树的根节点
TreeNode* new_node=CreateTreeNode(pre_order[*pre_order_index]);
//查找当前节点在中序遍历中的位置
size_t cur_root_in_order_index=Find(in_order,in_order_left,in_order_right,new_node->data);
//使用断言,判断该索引值是否合法
assert(cur_root_in_order_index!=-1);
//将先序遍历的索引值pre_order_index++,表示遍历下一个节点
(*pre_order_index)++;
//左子树的区间[in_order_left,cur_root_in_order_index)
new_node->lchild=_TreeRebuild(pre_order,pre_order_size,pre_order_index,in_order,in_order_left,cur_root_in_order_index);
//右子树的区间[cur_root_in_order_index+1,in_order_right)
new_node->rchild=_TreeRebuild(pre_order,pre_order_size,pre_order_index,in_order,cur_root_in_order_index+1,in_order_right);
return new_node;
}
size_t Find(TreeNodeType array[],size_t left,size_t right,TreeNodeType to_find)
{
size_t i=left;
for(;i<right;i++)
{
if(array[i]==to_find)
{
return i;
}
}
return -1;
}