[NOIP 2001 普及组] 求先序排列

GitHub跳转原题关系图返回列表

利用后序末尾字符确定根,再在中序里切出左右子树区间,递归按根左右顺序输出先序遍历。

OJ: luogu

题目 ID: P1030

难度:普及-

标签:树形结构递归二叉树python

日期: 2026-06-19 20:15

题意

给出同一棵二叉树的中序遍历和后序遍历,要求输出这棵树的先序遍历。

思路

最直接的办法是先把整棵树建出来,再做一次先序遍历。

先看一个可以直接验证想法的朴素解:

cpp
#include <bits/stdc++.h>
using namespace std;

struct Node {
    char val;
    Node *left;
    Node *right;
    Node(char c) : val(c), left(nullptr), right(nullptr) {}
};

// 直接按定义切字符串建树,适合小数据验证与教学理解。
Node* build(const string &inorder_str, const string &postorder_str) {
    if (inorder_str.empty()) {
        return nullptr;
    }

    char root = postorder_str.back();
    int mid = (int)inorder_str.find(root);
    Node *node = new Node(root);

    string left_in = inorder_str.substr(0, mid);
    string right_in = inorder_str.substr(mid + 1);
    string left_post = postorder_str.substr(0, left_in.size());
    string right_post = postorder_str.substr(left_in.size(), right_in.size());

    node->left = build(left_in, left_post);
    node->right = build(right_in, right_post);
    return node;
}

void preorder(Node *node, string &ans) {
    if (node == nullptr) {
        return;
    }
    // 先序遍历:根、左、右。
    ans.push_back(node->val);
    preorder(node->left, ans);
    preorder(node->right, ans);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    string inorder_str, postorder_str;
    cin >> inorder_str >> postorder_str;

    Node *root = build(inorder_str, postorder_str);
    string ans;
    preorder(root, ans);
    cout << ans << '\n';
    return 0;
}

brute.cpp 用字符串切片递归建树,然后再先序遍历,适合帮助理解。

正式解没必要真的把树节点一个个建出来。关键观察是:

  • 后序遍历的最后一个字符一定是根
  • 在中序遍历中找到根后,左边就是左子树,右边就是右子树

这张树形图正好对应题目样例:

graph TD
  A --> B
  A --> C
  C --> D

从图中可以直观看出:后序串 BDCA 的最后一个字符 A 是根。它把中序串切成左边 B 和右边 DC 两段。于是左子树大小就固定了,后序串里左右子树对应的区间也随之确定。

因此可以写一个区间递归函数:

  • 当前后序区间 post_l..post_r
  • 当前中序区间 in_l..in_r

先输出根,再递归处理左子树,最后递归处理右子树,就正好得到先序遍历。

Python 知识

  • 后序字符串的 postfix[-1] 直接取得根。
  • 字符串切片把左右子树的中序、后序片段交给下一层。
  • 递归函数返回 根 + 左先序 + 右先序,无需显式建树。
  • /home/rainboy/mycode/hugo-blog/content/program_language/python/input_output_and_strings.md:字符串下标、切片与拼接。
  • /home/rainboy/mycode/hugo-blog/content/program_language/python/brute_force_validation.md:递归分解。

代码

python
inorder = input().strip()
postorder = input().strip()


def preorder(infix, postfix):
    if not infix:
        return ""
    root = postfix[-1]
    middle = infix.index(root)
    return (
        root
        + preorder(infix[:middle], postfix[:middle])
        + preorder(infix[middle + 1:], postfix[middle:-1])
    )


print(preorder(inorder, postorder))

复杂度

字符串查找和切片使最坏时间复杂度为 O(n2)O(n^2),空间复杂度为 O(n2)O(n^2)。本题最多 8 个节点,直接切片能更清楚地展示左右子树划分。

总结

这题的核心就是“后序定根,中序分左右”。一旦看出这一点,先序遍历只是在递归顺序上改成“根左右”而已。

一图流解析

这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

一图流解析