PAT甲组1119 Pre- and Post-order Traversals 思路解析和代码

A1119

题目链接

个人思路

本题考查树的基本操作+树的遍历+树的重构,这道题还是很有挑战性的,花了我一2个小时的时间。
拿到题目后人是懵逼的状态,以前只知道前序/后序和中序唯一确定一棵二叉树,但给出前序和后序,如何重构这棵树呢?
先来回顾前序/后序&中序如何唯一确定二叉树的

  • 前序/后序的作用是提供根节点
  • 中序的作用是根据根节点的位置来划分左右子树的
  • 递归重构二叉树的左右子树即可

因此根据遍历结果重构二叉树的关键在于: 明确根节点并划分左右子树

前序+后序如何确定一棵二叉树:关键在于借助后序遍历中的根节点前一个结点来划分左右子树
无法唯一确定一棵树的原因:后序序列中 根节点前一个结点的位置 与 前序序列中树的根节点位置 相邻,导致无法判断当前子树是左子树还是右子树(即无法划分左右子树)

举个例子,配合画图有助于理解
例:
pre[] :1 2 3 4 6 7 5
post[]: 2 6 7 4 5 3 1
唯一确定in[]: 2 1 6 4 7 3 5(可以先画图帮助理解)

前提:明确两个根节点的含义

  • 四个参数:preL, preR, postL, postR
  • 先序遍历:根,左,右;后序遍历:左,右,根
  • pre[preL] == post[postR]均表示树的根节点
  • post[postR - 1]表示右子树的根节点(即根节点前一个结点,由于后序的遍历顺序是左,右,根)

思路(以第一次递归迭代为例)

  • 后序序列post[postR] == 1表示整棵树的根节点;post[postR - 1] == 3表示右子树的根节点
  • 遍历前序序列,找到右子树根节点的位置,即“3”的下标(pre[2] == 3)
    • 此时前序序列中,右子树的根节点与树的根节点之间存在其他节点,因此可以对左右子树进行划分,“3”之前的均为左子树,“3”之后包括“3”均为右子树,此时可以唯一确定一棵树(因为已经明确了根节点,并划分清楚了左右子树)
    • 考虑,如果右子树的根节点与树的根节点相邻(中间无其他节点),此时还能唯一确定一棵树么?答案是不能了。若二者相邻,就无法判断“3”后面的节点(包括“3”在内)到底是挂在树的根节点的左子树上还是右子树上(将例子中的“2”抹掉画图试试)

个人思路代码(Bug)

#include <bits/stdc++.h>
using namespace std;
int N;
int pre[105], post[105];
struct Node{
	int data;
	int left, right;
}nodes[105];
int pos;
int newNode(int x)
{
	nodes[pos].data = x;
	nodes[pos].left = nodes[pos].right = -1;
	return pos++;
}

//判断二叉树是否唯一,并返回左右子树划分位置的下标 
int checkUnique(int preL, int preR, int postL, int postR)
{
	//注意序列下标从0开始 
	int firRoot = pre[preL];//树的根节点,pre[0] = post[N - 1] 
	int secRoot = post[postR - 1];//获取后序序列根节点的前一个节点 
	if(pre[preL + 1] == secRoot)
		return -1;
	for(int i = preL + 2; i <= preR; ++i)
	{
		if(pre[i] == secRoot)
			return i;
	}
}

bool isUnique = true;//标记树是否唯一 
int recreate( int preL, int preR, int postL, int postR)
{
	//递归边界
	if(preL > preR || postL > postR || preL < 0 || preR < 0 || postL < 0 || postR < 0 || preR >= N || postR >= N)
		return -1;
	int root = newNode(post[postR]);//根据后序获得根节点
	int secroot = checkUnique(preL, preR, postL, postR);
	if(secroot == -1)
	{
		isUnique = false;
		nodes[root].left = recreate(preL + 1, preR, postL, postR - 1);
	}
	else
	{
		int numleft = secroot - preL - 1;//在pre[]中划分左右子树 
		nodes[root].left = recreate(preL + 1, preL + numleft, postL, postL + numleft - 1);
		nodes[root].right = recreate(preL + 1 + numleft, preR, postL + numleft, postR - 1);
	}
	return root;	
}
vector<int> in;
void inOrder(int root)
{
	//递归边界 
	if(root == -1)
		return;
	inOrder(nodes[root].left);
	in.push_back(nodes[root].data);
	inOrder(nodes[root].right);
}

int main(int argc, char *argv[]) {
	scanf("%d", &N);
	for(int i = 0; i < N; ++i)
	{
		scanf("%d", &pre[i]);
	}
	for(int i = 0; i < N; ++i)
	{
		scanf("%d", &post[i]);
	}
	int root = recreate(0, N - 1, 0, N - 1);
	if(isUnique == false)
		printf("No\n");
	else
		printf("Yes\n");
	inOrder(root);
	for(int i = 0; i < in.size(); ++i)
	{
		printf("%d", in[i]);
		if(i < in.size() - 1)
			printf(" ");
		else
			printf("\n");
	}
	return 0;
}

在这里插入图片描述

上述代码漏洞

修改后,仍有三个测试点未通过
checkUnique()缺少最后的返回值报错
原因:当输入的树不满足checkUnique判断条件,且无法进入循环时,该函数没有返回值;如输入1个结点时,此时便无返回值;输入1个结点若返回-1,会遇到下面的bug

修改后的代码(Bug)

#include <bits/stdc++.h>
using namespace std;
int N;
int pre[105], post[105];
struct Node{
	int data;
	int left, right;
}nodes[105];
int pos;
int newNode(int x)
{
	nodes[pos].data = x;
	nodes[pos].left = nodes[pos].right = -1;
	return pos++;
}
//判断二叉树是否唯一,并返回左右子树划分位置的下标 
int checkUnique(int preL, int preR, int postL, int postR)
{
	//注意序列下标从0开始 
	int firRoot = pre[preL];//树的根节点,pre[0] = post[N - 1] 
	int secRoot = post[postR - 1];//获取后序序列根节点的前一个节点 
	if(pre[preL + 1] == secRoot)
		return -1;
	for(int i = preL + 1; i <= preR; ++i)
	{
		if(pre[i] == secRoot)
			return i;
	}
	return -1;
}
bool isUnique = true;//标记树是否唯一 
int recreate( int preL, int preR, int postL, int postR)
{
	//递归边界
	if(preL > preR || postL > postR)
		return -1;
	int root = newNode(post[postR]);//根据后序获得根节点
	int secroot = checkUnique(preL, preR, postL, postR);
	/*int secroot = preL + 1;
	for(; secroot <= preR; ++secroot)
	{
		if(pre[secroot] == post[postR - 1])
			break;
	}*/
	if(secroot == -1)
	{
		isUnique = false;
		nodes[root].right = recreate(preL + 1, preR, postL, postR - 1);
	}
	else
	{
		int numleft = secroot - preL - 1;//在pre[]中,secroot位置划分左右子树 
		nodes[root].left = recreate(preL + 1, preL + numleft, postL, postL + numleft - 1);
		nodes[root].right = recreate(secroot, preR, postL + secroot - preL - 1, postR - 1);
	}
	return root;	
}
vector<int> in;
void inOrder(int root)
{
	//递归边界 
	if(root == -1)
		return;
	inOrder(nodes[root].left);
	in.push_back(nodes[root].data);
	inOrder(nodes[root].right);
}
int main(int argc, char *argv[]) {
	scanf("%d", &N);
	for(int i = 0; i < N; ++i)
	{
		scanf("%d", &pre[i]);
	}
	for(int i = 0; i < N; ++i)
	{
		scanf("%d", &post[i]);
	}
	int root = recreate(0, N - 1, 0, N - 1);
	if(isUnique == false)
		printf("No\n");
	else
		printf("Yes\n");
	inOrder(root);
	for(int i = 0; i < in.size(); ++i)
	{
		printf("%d", in[i]);
		if(i < in.size() - 1)
			printf(" ");
		else
			printf("\n");
	}
	return 0;
}

在这里插入图片描述

再次修复BUG

这次问题出在了recreate()的递归边界上,除了L > R作为边界,还有L == R,也要返回根节点
原因:checkUnique()无法处理preL == preR的情况,若输入的树只有一个结点,根据checkUnique()会返回-1,说明树不唯一,此时出错
注意

  • 当无法唯一确定一棵树时,将子树默认挂在左子树上或者右子树上均是对的
  • 只要过程中出现一次根节点与根节点前一个节点相邻的情况,结果就不唯一
  • 要注意行末要有换行,否则会出现格式错误

AC代码

#include <bits/stdc++.h>
using namespace std;
int N;
int pre[105], post[105];
struct Node{
	int data;
	int left, right;
}nodes[105];
int pos;
int newNode(int x)
{
	nodes[pos].data = x;
	nodes[pos].left = nodes[pos].right = -1;
	return pos++;
}
//判断二叉树是否唯一,并返回左右子树划分位置的下标 
int checkUnique(int preL, int preR, int postL, int postR)
{
	//注意序列下标从0开始 
	int firRoot = pre[preL];//树的根节点,pre[0] = post[N - 1] 
	int secRoot = post[postR - 1];//获取后序序列根节点的前一个节点 
	if(pre[preL + 1] == secRoot)
		return -1;
	for(int i = preL + 1; i <= preR; ++i)
	{
		if(pre[i] == secRoot)
			return i;
	}
	return -1;
}
bool isUnique = true;//标记树是否唯一 
int recreate( int preL, int preR, int postL, int postR)
{
	//递归边界
	if(preL > preR || postL > postR)
		return -1;
	if(preL == preR)//递归边界
	{
		int root = newNode(post[postR]);
		return root;
	} 
	int root = newNode(post[postR]);//根据后序获得根节点
	int secroot = checkUnique(preL, preR, postL, postR);
	if(secroot == -1)
	{
		isUnique = false;
		nodes[root].right = recreate(preL + 1, preR, postL, postR - 1);
	}
	else
	{
		int numleft = secroot - preL - 1;//在pre[]中,secroot位置划分左右子树 
		nodes[root].left = recreate(preL + 1, preL + numleft, postL, postL + numleft - 1);
		nodes[root].right = recreate(secroot, preR, postL + secroot - preL - 1, postR - 1);
	}
	return root;	
}
vector<int> in;
void inOrder(int root)
{
	//递归边界 
	if(root == -1)
		return;
	inOrder(nodes[root].left);
	in.push_back(nodes[root].data);
	inOrder(nodes[root].right);
}
int main(int argc, char *argv[]) {
	scanf("%d", &N);
	for(int i = 0; i < N; ++i)
	{
		scanf("%d", &pre[i]);
	}
	for(int i = 0; i < N; ++i)
	{
		scanf("%d", &post[i]);
	}
	int root = recreate(0, N - 1, 0, N - 1);
	if(isUnique == false)
		printf("No\n");
	else
		printf("Yes\n");
	inOrder(root);
	for(int i = 0; i < in.size(); ++i)
	{
		printf("%d", in[i]);
		if(i < in.size() - 1)
			printf(" ");
		else
			printf("\n");
	}
	return 0;
}

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值