一,前言
本文是原创作品,可能存在不足,欢迎大家指正,礼貌交流,感激不尽。
二,思路
二叉树后序遍历不同于前序遍历和中序遍历,后序遍历是三者之间最难的。下面就让我们来梳理一下代码的逻辑,看看如何轻松地理解代码该咋写。
约定:
1,整个二叉树所有叶子结点的孩子均为NULL。
2,二叉树中的结点被定义为一个结构体node。
结构如下:
typedef struct node0{
node0* left;
node0* right;
char data;
}node;
3,整个二叉树不为空。
(一)双指针法
简单过程就是:如果设置一前一后两个指针分别为prior和now,如果当前没有触及到二叉树的底部,就让它们保持父子关系一直向左前进。如果左边触底了,就向右边前进一步,然后继续向左前进。如果左右都触底了,那就回退:当now是prior的左孩子时回退一步,当now是prior的右孩子时一直回退。回退时不断输出经过的节点。回退完了之后让now指向prior的右孩子。循环执行上述过程即可。
下面就是具体解释:
这个方法的主要思路是,利用前后两个指针来方便地判断什么时候退栈一次,什么时候需要一直退栈。(这两个指针互为前序和后继)
首先,定义两个指针pre和now,now代表当前访问的结点。
图解如下:
初始时now是pre的孩子(下图中已经让二者前进一步了)。
只要左边有分支,就往左边走。左边没有路就往右边走一步,然后再往左边走。
如果左右都为空:
这时,如果now是pre的左孩子,就输出pre结点并回退一步,now紧跟其后。
回退后:
如果now是pre的右孩子,就一直回退并输出pre结点,now紧跟其后。直到now不是pre的右孩子,或者退无可退(此时程序结束)。
回退后:
回退完了之后,让now指向pre的右孩子结点。
循环往复上述过程即可。
具体逻辑如下:
以下,now指针触底的含义是,“pre的左右孩子均为NULL”或者“一个孩子为NULL并且另一个孩子已经被访问过”,此时now是pre的孩子。
初始时设定now是pre的左孩子(假设整个二叉树不为空)
(一) 当now是pre的孩子时:
此时,pre指向栈顶元素。
1,当now没有触底时,如果now有左孩子,就让now一直往左子树走,同时压栈,直到now==NULL,pre紧随其后;若now==NULL,尝试往pre的右孩子的方向走一步,接着继续往左孩子的方向走,除非右边也没有路了(pre的左右孩子均为NULL)。
2,pre的左右孩子均为NULL时,now已经触底。交换pre和now的位置。
(二)当pre是now的孩子时:
此时now指向栈顶元素。
接下来要开始退栈,退栈的过程中将now指向栈顶元素,pre紧跟其后(二者轨迹相同)。
如果pre是now的左孩子,就退栈一次(退栈就要输出栈顶元素)。
如果pre是now的右孩子,就一直退栈(退栈就要输出栈顶元素),直到pre不是now的右孩子或者栈空(栈空就结束程序运行)。
退栈完了之后,pre是now的左孩子。此时让now指向它自己的右孩子,pre紧跟其后(轨迹相同)。
(三)整个过程中,只要栈空就停止程序运行,代表输出结束。
(二)双栈法
由于后序遍历的输出顺序是左->右->根,倒过来就是根->右->左。因此,只按照根->右->左的访问顺序访问一次,并存入栈中,就能实现倒过来输出了。非递归需要一个栈,倒过来需要一个列表(输出列表,也是一个栈),因此需要两个栈。
三,代码
#include<iostream>
#include<stdlib.h>
#include<math.h>
using namespace std;
int amount = 15;
typedef struct node0{
node0* left;
node0* right;
char data;
}node;
node* init(int num)//初始化函数,建立一个4层高的满二叉树,各个结点按层次遍历顺序依次命名为:ABCDEF...LMNO
{
int i,j;
int tree_high = 4;//树的总高
node* tree = (node*)malloc(sizeof(node)*amount);
for(i=1;i<=num;i++)//i从1到15
{
tree[i].data = 64 + i;
if(i<=pow(2,tree_high-1)-1)
{
tree[i].left = &tree[2*i];
tree[i].right = &tree[2*i+1];
}
else
{
tree[i].left = NULL;
tree[i].right = NULL;
}
}
return (tree+1);
}
void show_tree3(node* now)//二叉树后序遍历递归算法
{
if(now->left != NULL) show_tree3(now->left);
if(now->right != NULL)show_tree3(now->right);
cout<<now->data<<",";
}
void last_scan1(node* head,int amount)//二叉树后序遍历算法一 ---- 双指针法
{
int top=-1;//top指针,指向栈顶元素
node *now,*pre=NULL;
node** stack = (node**)malloc(sizeof(node*)*(amount+1));
//指针数组,保证存进stack里面的都是前面建立的二叉树的节点,而不是它们的复制
now = head;
stack[++top] = now;
now = now->left;
while(top >= 0)
{
while(now != NULL)
{
stack[++top] = now;
pre = now;
now = now->left;
}
if(pre->right != NULL)
{
now = pre->right;
continue;
}
//此时,已经触底
while(pre->right == now )//当右子树已经被访问完,就去访问根节点
{
cout<<pre->data<<",";
top--;
if(top < 0)return;
now = pre;
pre = stack[top];
}
if(pre->left->data == now->data)
{
now = pre->right;
}
}
}
void last_scan2(node* head,int amount)//后序遍历二叉树,双栈法
{
int top = -1;//栈顶指针,指向栈顶元素
int top2 = -1;//输出列表顶端指针,指向输出列表最后一个元素
node* now;
cout<<"双栈法后序遍历二叉树的结果为:"<<endl;
node* stack = (node*)malloc(sizeof(node)*(amount+1));//栈
node* lst = (node*)malloc(sizeof(node)*(amount+1));//输出列表
now = head;
stack[++top] = *now;
lst[++top2] = *now;
now = now->right;
while(top >= 0)
{
while(now != NULL)
{
lst[++top2] = *now; //将元素压入到列表中,以便反向输出
stack[++top] = *now;
now = now->right;
}
if(stack[top].left != NULL)
{
now = stack[top].left;
continue;
}
//此时,stack[top]已经指向叶子结点
while(top>0 && stack[top-1].left->data == stack[top].data )
{
top--;//左孩子就一直退栈
}
if(top <= 0)break;
if(stack[top-1].right->data == stack[top].data)
{
top--;//右孩子就只退一次栈
}
now = stack[top].left;
}
for(;top2>=0;top2--)
{
cout<<lst[top2].data<<",";
}
}
int main()
{
node* head = init(amount);
cout<<"递归法后序遍历二叉树"<<endl;
show_tree3(head) ;
cout<<"\n双指针法后序遍历二叉树"<<endl;
last_scan1(head,amount);
cout<<endl;
last_scan2(head,amount);
free(head);
return 0;
}