二叉树的遍历有两种方式,一种是递归遍历,另一种是非递归遍历。
前一种方法从形式上很好理解,在这里不再过多的谈论,直接上代码。
二叉树的建立
//定义并通过层序生成一棵二叉树
typedef struct BiTree{
int value;
struct BiTree* left;
struct BiTree* right;
}BiTree;
BiTree* CreatBiTree(){
int arr[6]={1,5,3,2,6,7};
BiTree** con[MAX]={};
int idxa=1;
int idxc1=0;
int idxc2=2;
for(int i=0;i<MAX;++i)con[i]=NULL;
BiTree *root=(BiTree*)malloc(sizeof(BiTree));
root->value=arr[0];
root->left=root->right=NULL;
con[0]=&(root->left);
con[1]=&(root->right);
while(idxa<6){
BiTree* q=(BiTree*)malloc(sizeof(BiTree));
q->value=arr[idxa++];
q->left=q->right=NULL;
*con[idxc1++]=q;
con[idxc2++]=&(q->left);
con[idxc2++]=&(q->right);
}
return root;
}
二叉树的递归遍历
int Pre(BiTree *T){
if(!T)return 0;
cout<<T->value<<dec;
if(T->left)Bef(T->left);
if(T->right)Bef(T->right);
}
int In(BiTree *T){
if(!T)return 0;
if(T->left)Mid(T->left);
cout<<T->value<<dec;
if(T->right)Mid(T->right);
return 1;
}
int Post(BiTree *T){
if(!T)return 0;
if(T->left)Beh(T->left);
if(T->right)Beh(T->right);
cout<<T->value<<dec;
return 1;
}
从代码中的形式中很容易理解遍历的顺序,根据”被遍历”节点的位置将节点进行打印输出。
二叉树的非递归遍历
int NPre(BiTree *T){
BiTree* stk[MAX]={};
int top=0;
BiTree *poped=NULL;
BiTree *q=T;
while(top>0||q){
if(q){
cout<<q->value<<dec;
stk[top++]=q;
q=q->left;
}
else{
q=stk[top-1]->right;
if(!q||q==poped){
top--;
poped=stk[top];
q=NULL;
}
}
}
}
int NIn(BiTree *T){
BiTree* stk[MAX]={};
int top=0;
BiTree* q=T;
while(q||top>0){
if(q){
stk[top++]=q;
q=q->left;
}
else{
cout<<stk[--top]->value<<dec;
q=stk[top]->right;
}
}
}
int NPost(BiTree *T){
BiTree *stk[MAX]={};
int top=0;
BiTree *q=T;
BiTree *poped=NULL;
while(q||top>0){
if(q){
stk[top++]=q;
q=q->left;
}
else{
if(stk[top-1]->right==NULL||stk[top-1]->right==poped){
top--;
cout<<stk[top]->value<<dec;
poped=stk[top];
q=NULL;
}
else q=stk[top-1]->right;
}
}
}
二叉树的非递归遍历和递归遍历本质上是相同的,只是存储未经过遍历变量的方式有所区别。接下来就详细的对二叉树的非递归遍历进行说明。
1.前序(中、左、右)
由其遍历的顺序可知,如果当前节点存在,则应先输出当前节点的值,将其保存在栈中,并将指针指向其左子树,对其左子树进行判断。
当其左子树为空时,对于当前的这棵树,父节点已经输出,左子树不存在,这时我们应该对其右子树进一步进行判断:1.若其右子树不存在,则说明当前父节点已经是左侧最深的节点了,此时我们应当将此节点弹出,并把指针指向null,做下一次判断(不能指回其父节点,否则会出现多余的遍历)2.若其右子树存在并且不等于刚刚弹出的节点(考虑一个节点的右子树不为空,但右子树的左右子树为空时,遍历完其右子树后,会弹出其右子树,此时若不将其设置为poped,并且在判断时加上对poped的判断,则其右子树会再一次被选为当前节点,于是死循环就出现了)则将其设置为当前节点,继续进行遍历。
当top=0时有两种情况,第一是包括根节点在内的所有根节点左边的节点遍历完成,另一种是全部遍历完成,此时需要对结束条件进一步判断,前两个的区别为top为0时”当前节点的情况”,如果是null则说明无更多的节点可以遍历了,如果非Null,则说明当前仍有节点为经遍历,需要继续遍历。
2.中序(左、中、右)
中序遍历需要我们首先对节点的左子树(如果存在的话)进行遍历,其次在遍历”本节点”,最后遍历右节点。
跟着定义出发,遍历时如果当前节点存在则将其放入栈,并将当前节点赋为其左子树,直到当前节点为空时,此时我们可以假定,此节点作为其父节点的左子树已经被遍历过了(虽然为空),所以根据顺序,我们应该对此节点的父节点进行遍历,首先将其打印出来表示以被遍历,此时我们遇到一个问题,是否要将其弹出呢?我们接着往下思考,1.若此节点没有右子树,则一定要将其弹出,否则程序无法继续执行。2.如果此节点有右子树,假设此时先不弹出,则我们接下来应该对其右子树进行遍历,某一时刻其右子树遍历完成并且都弹出,又到了这个节点,所以此时需要对其做判断,但很显然这是多余的。综合以上两点,我们将其弹出。此时对此节点的右子树进行判断,如果为null,则将当前指针指向null,如果不为null,则将其指向右子树。
对于结束条件的判断与1相同。
3.后序(左、右、中)
由于本次也是先读出节点的左子树,所以前一部分与2相同,我们直接讨论当当前指针为空的情况。当前指针指向null时,表示此节点父节点的左子树作为null已被遍历,此时应该考虑,其父节点的右子树的情况。进行一下两类判断。1.当其父节点的右子树为空时,表示此节点为”最左的节点”,将其打印并弹出,表示为以被遍历,接下来我们应该考虑其父节点的父节点的右子树的情况,到此我们只需要将当前节点设为null即可。2.当其父节点的右节点存在时,由遍历顺序可知,此时还无法对此节点的父节点及其右子树进行打印,需要进一步判断,所以将当前节点赋为其父节点的右子树。
此时我们考虑一种特殊情况,当一个节点和其右子树存在,并且其右子树的左右子树不存在时,当遍历完其右子树,弹出其右子树后,对此节点进行判断,会再将其右子树纳入当前节点,于是又陷入了死循环,所以在后序遍历时,我们仍然需要对刚遍历的节点进行记录,并在当前节点为空,对下一当前节点进行赋值时,加上此判断。情况与前序是一致的。
总结,当我们使用非递归方法对二叉树进行遍历时,需要着重注意的是以下几点。
1.何时打印出遍历值
2.当”当前指针”指向null时,接下来对”当前指针”指向的判断,我们不需要记住,写的时候适当进行推理便很容易得出。
3.是否需要记录已弹出的变量,我们根据这篇文章的分析可以知道,当前序(中左右)和后序(左右中)时需要对其判断,从二者遍历形式上来看,这两种遍历的”顺序”是一致的,”左”和”右”相邻,这两个遍历方式还有许多相似之处,比如,仅当中序便利和这两个中任一序遍历已知时,我们才能推出二叉树的形状等…在此就不在多说。
4.结束条件的判断。
只要注意好上述四点,我们再写出非递归遍历时就会很容易了。
本文详细介绍了二叉树的递归和非递归遍历方法,包括前序、中序和后序遍历的实现逻辑。通过分析遍历顺序,阐述了在非递归遍历中如何判断节点状态、何时打印节点值以及如何避免死循环,强调了遍历过程中需要注意的关键点。
1万+

被折叠的 条评论
为什么被折叠?



