二叉树的非递归后序遍历(双栈法和双指针法,有图有真相)

一,前言

本文是原创作品,可能存在不足,欢迎大家指正,礼貌交流,感激不尽。

二,思路

二叉树后序遍历不同于前序遍历和中序遍历,后序遍历是三者之间最难的。下面就让我们来梳理一下代码的逻辑,看看如何轻松地理解代码该咋写。

约定:

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;
} 

测试所用二叉树结构如下:

四,运行结果

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值