由中序和先序(后序)非递归重建二叉树 时间复杂度O(n)

2 篇文章 0 订阅
1 篇文章 0 订阅

先序和中序参考另一篇文章蓝桥杯试题 算法训练 绘制地图—O(n)复杂度重建整棵树

和由前序中序推后序类似

输入:

第一行一个整数n,表示节点编号1~n。
第二行n个整数,表示后序遍历。
第三行n个整数,表示中序遍历。
算法过程:
post[n]为后序序列 in[n]为中序序列 vis[n]记录是否已在后序序列中出现

初始两个下标cur1=n-1,cur2=n-1
1.当post[cur1]!=in[cur2]时 cur1往后退一格,并且post[cur1]号结点的右孩子为post[cur1-1]号结点 并记录vis[post[cur-1]]=1。

while (in[cur2] != post[cur1]) {
	tree[post[cur1]].right = post[cur1 - 1];
	vis[post[cur1 - 1]] = 1;
	cur1--;
}

2.此时post[cur1]==in[cur2],我们让cur2后退,直到in[cur2]号结点未出现过,即vis[in[cur2]]=0

while (vis[in[cur2]]) {
	cur2--;
}

3.这时令in[cur2+1]号结点的左孩子为pre[cur1-1],cur1后退一格

tree[in[cur2 + 1]].left = post[cur1 - 1];
vis[post[cur1 - 1]] = 1;
cur1--;


重复1,2,3 直至cur1或cur2走到0。

下面演示一组例子执行过程 

绿色代表中序数组的进展 黄色代表通过步骤3连结的结点

注意在最后一张图片中 后序数组已经走到起点 即cur1已经为0(中序数组没有走完)


下面证明

每次中序序列后退时第一个未在后序序列中出现的节点的后一个结点(cur2+1)是后序序列中下一个结点(cur1-1)的父亲(父亲和左孩子)
 

由于该点的后一个点(cur2+1)在已经遍历过的后序序列中出现,那么因为中序遍历的性质,后一个点的右子树上任一个点必在该之后被遍历到,即后一点右子树上任意一点已在后序序列中出现过,并且由中序遍历性质还可得到该点为后一个点的左子树上的结点(反证法:显然该点不可能在前一点的右子树,如果为前一点的父亲/长辈结点,那么该点一定在后序序列中出现过 从而产生矛盾,这是因为后序遍历任何一个结点必在其子结点后被遍历到)而且为左子树上最右的节点。那么因为在后序序列中(从后往前)左子树上最右的结点都没有出现过,从而整棵左子树上的结点都没有出现过(反过来就是都已被遍历完),综上:
1.后一个结点(cur2+1)的右子树在后序遍历中未被遍历过
2.后一个结点(cur2+1)的左子树在后序遍历中已遍历完  
(注意这里遍历完的意思是从前往后的顺序已经遍历完)
那么可以得出结论中序序列后一个点的左孩子就是后序遍历序列中的下一个结点(从后往前)。
这里多解释一下,后一个节点的左子树的根节点在后序遍历中一定最后被遍历到,那么在序列中的顺序就是从后往前的下一个结点。

复杂度分析:显然这里cur1,cur2不会回退时间复杂度O(n);

其次,对于无法通过过序列的值访问结点的情况,即步骤二无法直接访问到对应结点的情况,可通过哈希方法记录对应序列的值和结点的指针,这样来时间复杂度仍是O(n)的。

#include<bits/stdc++.h>
using namespace std;
int	n;
struct bitnode {
	int left, right;
	bitnode() :left(0), right(0) {}
};
const int maxn = 1e5 + 5;
int post[maxn], in[maxn], vis[maxn];
int root;
bitnode tree[maxn];
void solve() {
	root = post[n-1];
	vis[post[n-1]] = 1;
	int cur1 = n-1, cur2 = n-1;
	while (cur1 > 0 || cur2 > 0) {
		//当post[cur1]!=in[cur2]时,cur1往后退一格
		//并且post[cur1]号结点的左孩子为post[cur1-1]号结点,并记录vis[post[cur-1]]=1。
		while (in[cur2] != post[cur1]) {
			tree[post[cur1]].right = post[cur1 - 1];
			vis[post[cur1 - 1]] = 1;
			cur1--;
		}
		//此时post[cur1]==in[cur2],我们让cur2后退
		//直到in[cur2]号结点未出现过,即vis[in[cur2]]=0
		while (vis[in[cur2]]) {
			cur2--;
		}
		//这时令in[cur2+1]号结点右孩子为pre[cur1-1],cur1后退一格 
		tree[in[cur2 + 1]].left = post[cur1 - 1];
		vis[post[cur1 - 1]] = 1;
		cur1--;
	}
}
void preorder(int root)
{
	cout << root << " ";
	if (tree[root].left) {
		preorder(tree[root].left);
	}
	if (tree[root].right) {
		preorder(tree[root].right);
	}
}
/*
9
5 4 2 6 8 9 7 3 1
4 5 2 1 6 3 8 7 9
*/
int main() {
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> post[i];
	}
	for (int i = 0; i < n; i++) {
		cin >> in[i];
	}
	clock_t s = clock();
	solve();
	clock_t e = clock();
	//cout << e - s;
	preorder(root);
	return 0;
}

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

強く進む

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值