西电数据结构上机题第三次机考练习题部分题目题解

Problem318二叉树遍历

题面

给定一棵二叉树的先序遍历和中序遍历序列,求其后序遍历序列。

解答

用递归的方式进行求解。
显而易见,任何一个子树的先序遍历序列的首字母一定是该子树的树根。而在中序遍历中,该树根左侧是左子树,右侧是右子树,那么我们只需要不断的递归分解左右子树,即可求出原树。
需要注意的是,由于我们需要求后序遍历序列,因此对于每一棵子树,都需要在它左右子树分别递归分解完成之后再输出树根结点。

code

void getnxt (string pre, string mid, int len) {
	if (len == 0) return ;//如果当前子树为空
	if (len == 1) {//如果当前是叶子结点
		cout << pre;//直接输出
		return ;
	}
	int k = mid.find (pre[0]);//找到中序遍历中根节点的位置
	getnxt (pre.substr (1, k), mid.substr (0, k), k);//遍历左子树
	getnxt (pre.substr (k + 1, len - 1 - k), mid.substr (k + 1, len - 1 - k), len - k - 1);//遍历右子树
	cout << pre[0];
}

这里的string的substr函数共有两个参数,例如pre.substr(1,k),意思是在取string类型pre的子串[1…k](从下标1处开始,长度为k)

Problem319将满二叉树转化为求和树

题面

在这里插入图片描述

解答

大致思路和Problem318一样,细节处理上需要花一些功夫。
这里,我们getnxt函数设置为int类型,以返回当前结点的左右子树之和。我们需要知道的是,对于每一个结点,它的求和树上对应的结点数值是它左右子树之和(不包括它本身),因此我们这里需要先把它本身的数值记录下来,再赋予它新值。特别地,对于每一个叶子结点,在将它的值返回上一层函数之前,需要将它赋零。
经过我们的操作,原中序遍历序列就成了答案要求的求和树中序遍历序列。

code

int find (int L, int R, int x) {
	for (int i = L;i <= R;i ++)
		if (b[i] == x) return i;
}
int getnxt (int l, int r, int L, int R) {
	
	int len = r - l + 1;//利用l和r算出当前子树的大小
	if (len == 0) return 0;//如果子树为空
	if (len == 1) {//如果是叶子结点
		int tmp = a[l];//先记录下当前叶子结点的数
		a[l] = b[L] = 0;//将前序遍历和中序遍历的叶子结点赋零
		return tmp;//返回叶子结点的值
	}
	int k = find (L, R, a[l]);//在中序遍历序列中找到根节点的位置
	int t1 = getnxt (l + 1, l + k - L, L, k - 1);//遍历左子树
	int t2 = getnxt (l + k - L + 1, r, k + 1, r);//遍历右子树
	int tmp = a[l];//先记录下当前结点的值
	a[l] = b[k] = t1 + t2;//将先序遍历序列和中序遍历序列中该结点赋值为两个子树值之和
	return tmp + a[l];//整个子树的值应为当前左右子树值、根节点值之和
}

Problem320二叉树的不同形态

题面

在这里插入图片描述

解答

这道题相对前两道题较复杂。一共分为两个步骤

  1. 根据层次遍历序列和中序遍历序列求出先序遍历序列
  2. 根据先序遍历序列和中序遍历序列求出后序遍历序列

第二步是前面介绍过的算法。这里着重介绍第一步的算法。
直观暴力思想
还是同之前一样,利用递归的手段进行左右子树拆解。但是不同的是,层次遍历序列不像先序遍历序列那样,可以非常容易得知每一棵子树的根节点。但是我们可以发现两个性质:

  1. 在层次遍历序列中,父亲结点永远出现在儿子节点的前面。
  2. 在层次遍历序列中,在父亲结点后出现的第一个存在于左子树的结点,就是该父亲节点的左子树根节点;右子树根节点亦然。

所以我们可以改进之前的算法:
若左子树不为空,当遍历左子树前,我们可以通过查找层次遍历序列中,父亲节点后,第一个出现的存在于左子树的结点,作为左子树的根节点,以它为依据划分中序遍历序列,继续拆解二叉树。

if (k - l > 0) {
		int t = root + 1;
		while (find (l, k - 1, dep[t]) == -1) t ++;
		getpre (l, k - 1, t);
	}

但是我们发现了一个问题,在寻找一个父亲节点的两个孩子结点时候,需要对层次遍历序列中,父亲孩子之间的所有结点都进行while (find (l, k - 1, dep[t]) == -1) t ++;的操作,粗略估计下时间复杂度大约是 O ( n ) = n 3 O(n)=n^3 O(n)=n3级别,算法还需要改进。我们很容易意识到一个特性:

  • 当一个结点在拆解过程中被作为父亲结点使用后,一定不会使用第二次。

因此我们可以设置一个新函数(其实就是一个bool类型的标记数组)used[x]。当x没有被当做父亲节点使用时,我们让他为0,代表它需要被查找;一旦被使用后让他为1,代表他不需要再被查找。于是代码可以改为:

if (k - l > 0) {
		int t = root + 1;
		while (used[t] || find (l, k - 1, dep[t]) == -1) t ++;
		used[t] = 1;
		getpre (l, k - 1, t);
	}

该算法在满二叉树的情况下效果最差,但是在题目的数据范围下经过自造数据的多次检验,可以在1s内完美跑完。

code

int find (int l, int r, int x) {
	for (int i = l;i <= r;i ++)
		if (mid[i] == x) return i;
	return -1;
}
int pre[maxn], top = 0;
int used[maxn];
void getpre (int l, int r, int root) {
	int len = r - l + 1;//当前子树大小
	if (len == 0) return;//如果子树为空
	if (len == 1) {//叶子节点
		printf ("%d ", mid[l]);
		pre[++ top] = mid[l];//在先序遍历序列中记录叶子结点
		return ;
	}
	int k = find (l, r, dep[root]);
	pre[++ top] = mid[k];//先序遍历序列,需要先记录根结点
	if (k - l > 0) {
		int t = root + 1;
		while (used[t] || find (l, k - 1, dep[t]) == -1) t ++;
		used[t] = 1;
		getpre (l, k - 1, t);
	}
	if (r - k > 0) {
		int t = root + 1;
		while (used[t] || find (k + 1, r, dep[t]) == -1) t ++;
		used[t] = 1;
		getpre (k + 1, r, t);
	}
}
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值