1020 Tree Traversals (25)(25 分)

本文介绍了一种巧妙的方法,利用后序遍历和中序遍历序列构建二叉树,并实现层次遍历输出。这种方法避免了直接构建二叉树的过程,通过递归确定每一层的根节点来间接完成层次遍历。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



题目

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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值