题目
Suppose that all the keys in a binary tree are distinct positive integers. Given the postorder and inorder traversal sequences, you are supposed to output the level order traversal sequence of the corresponding binary tree.
Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (<=30), the total number of nodes in the binary tree. The second line gives the postorder sequence and the third line gives the inorder sequence. All the numbers in a line are separated by a space.
Output Specification:
For each test case, print in one line the level order traversal sequence of the corresponding binary tree. All the numbers in a line must be separated by exactly one space, and there must be no extra space at the end of the line.
Sample Input:
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7
Sample Output:
4 1 6 3 5 7 2
题目链接: https://pintia.cn/problem-sets/994805342720868352/problems/994805485033603072
解题思路
给出后序和中序,然后求层次遍历,没什么特别难的,一般给出两种遍历序列,求另一种序列的程序思路都是根据两种序列求出树的原型(建树),然后再重新遍历得到所求序列。
但是看了柳婼大神的代码,她给出了一种更巧妙的算法,短小精悍,看了半天才看懂,把自己的理解过程写一下——
中序的结构的特点是:左子树+根结点+右子树。
而后序结构的特点是:左子树+右子树+根结点。
且,这个结构可以递归向下定义,在左右子树中也符合这个结构。
那么,如果能够按顺序得到各层的根结点,其实也就间接求得了层次遍历的序列。
后序遍历的最后一个一定是根结点,以此类推,后序遍历的左右子树的最后一个元素一定是子树所在的根结点(如果存在的话),也就是第二层的结点,以此类推,可以得到各层的结点。
但是,但是,但是——此时面临一个问题,如何在后序遍历中区分左右子树的边界呢?
显然,这个时候,中序遍历发挥了作用,要知道,中序遍历是“左+中+右”的结构,后序遍历的最后一个元素提供了根的值,也就是说,可以根据这个根结点的结点值(不是位置,而不是索引,是data),查找中序遍历中根的位置,然后根据这个位置,得到左右字数的相对长度。从定义上不难看出,左右子树的长度,在前序、中序、后序这三种遍历中的长度是相等的,不变的。那么中序遍历求到的左右子树的长度可以应用在后序遍历序列中,就可以得到左右子树的边界,也就得到了左右子树的根结点的索引。以此类推,可以得到所有层次的结点。(可能讲的不是很清楚,改天有空补几张图,更形象一点)。
注意,在使用上述方法往下记录结点的时候,层次遍历的结点数组下标迭代,一定是2*index
,而不是index++
,因为我们必须考虑满二叉树的情况,保证每个节点都能够被存储。
显然,这种非常规手段仅适用于所有结点值不相同的情况,如果结点值相同,那么需要更复杂的手段去区分。
AC代码
#include <iostream>
#include <vector>
using namespace std;
vector<int> post, in, level(100000, -1);
void pre(int root, int start, int end, int index) {
cout<<"root = "<<root<<" start"<<start<<" end="<<end<<" index="<<index<<endl;
if(start > end) return ;
int i = start;
// 通过后序根节点找到中序根节点的索引
while(i < end && in[i] != post[root]) i++;
// 对于后序遍历,最后一个结点是根节点
level[index] = post[root];
// 【这段递归是本代码的亮点所在】
// (root -(end - i + 1)) 后序root地址 - (中序左子树长度),得到下一次的左子树的后序root地址
pre(root - (end - i + 1), start, i - 1, 2 * index + 1);
// root - 1 后序root地址 左邻接点右子树的根
pre(root - 1, i + 1, end, 2 * index + 2);
}
int main() {
int n, cnt = 0;
scanf("%d", &n);
// 预置n个结点
post.resize(n);
in.resize(n);
for(int i = 0; i < n; i++) scanf("%d", &post[i]);
for(int i = 0; i < n; i++) scanf("%d", &in[i]);
pre(n-1, 0, n-1, 0);
for(int i = 0; i < level.size(); i++) {
//
if (level[i] != -1) {
if (cnt != 0) printf(" ");
printf("%d", level[i]);
cnt++;
}
if (cnt == n) break;
}
return 0;
}