中序序列:ABEDFCHG
先序序列:CBADEFGH
方法一
先序遍历每次先输出跟节点,于是我们可以根据先序序列的顺序向下构造。
中序序列先输出左子树,在输出根节点,最后是右子树,其保留了树的左右顺序结构。
我们来看一个例子:
由先序序列的首符号得知C是整颗二叉树的根节点,此时可以去查中序序列,在C左边的字符都在C的左子树当中,C右边的符号都在C的右子树当中。
于是我们可以使用以下算法先还原出二叉树:
-
依次从先序序列中取出字符c
-
查找到c的父亲,并且确定c是其左还是右child
-
将c添加到树中
其中最关键的地方第2步,即如何确定c的父亲是谁c是其左还是右child,下面将在程序设计中详细介绍
程序设计
为了查找c应该插入的位置,我们从根节点查起
在确定某一字符c的父亲是谁之前,我们可以先尝试确定c在当前遍历到的节点的左子树还是右子树,假设确定c在左子树中,会分为以下情况:
-
当前节点左为空
-
当前节点左不为空
1情况表明当前节点即为c的父亲,2情况表明还需继续查当前节点的左子树
有了以上思想以后,我们定义二叉树的节点为:
struct node {
node(char c) : val(c) { //构造函数
left = nullptr;
right = nullptr;
}
char val; //当前节点的值
node *left, *right; //左右子树指针
pair<int, int> leftlist, rightlist;
};
与普通二叉树不同的地方在于我们引入了leftlist 和 rightlist,因为已知中序序列,于是我们可以用字符串的索引记录当前节点的左子树,右子树都有哪些符号
还是刚才的例子
中序序列:ABEDFCHG
先序序列:CBADEFGH
对于根节点C,他的leftlist就是[0, 5), rightlist是[6,7)
先写出整体框架
node *root = new node(pre[0]);
init_node(root, {0, pre.size()}); //初始化根节点
for (int i = 1; i < pre.size(); ++i) {
char curchar = pre[i];
auto temp = find(root, curchar); //找到应该插入的位置
node* target = temp.first;
bool isleft = temp.second; //是否插入到左边
if (isleft) {
target->left = new node(curchar);
init_node(target->left, target->leftlist);
}
else {
target->right = new node(curchar);
init_node(target->right, target->rightlist);
}
}
其中find函数用于寻找到插入的位置,init_node的作用是确定当前节点的左子树右子树都有哪些符号,即确定leftlist和rightlist
首先来看find函数
pair<node*, bool> find(node* root, char c) {
auto lpos = root->leftlist;
auto rpos = root->rightlist;
node* rt = nullptr;
bool rtleft;
//先查是否在左子树中
for (int i = lpos.first; i < lpos.second; ++i) {
if (middle[i] == c) {
if (root->left != nullptr) { //如果左子树不为空,还需继续向下找
auto [temp, isleft] = find(root->left, c);
if (temp != nullptr) {
rt = temp;
rtleft = isleft;
break;
}
}
rt = root;
rtleft = true;
break;
}
}
if (rt != nullptr) return {rt, rtleft};
//找右子树逻辑同左子树
for (int i = rpos.first; i < rpos.second; ++i) {
if (middle[i] == c) {
if (root->right != nullptr) {
auto [temp, isleft] = find(root->right, c);
if (temp != nullptr) {
rt = temp;
rtleft = isleft;
break;
}
}
rt = root;
rtleft = false;
break;
}
}
if (rt != nullptr) return {rt, rtleft};
return {nullptr, false};
}
init_node函数
void init_node(node* cur, pair<int, int> pos) {
int key = pos.first;
//寻找到当前节点的位置
while (key < pos.second && middle[key] != cur->val) {
key++;
}
cur->leftlist = {pos.first, key};
cur->rightlist = {key + 1, pos.second};
}
在中序序列中,当前用节点位置将其父节点的序列一分为二,左部为左子树拥有符号,右部为右子树拥有的符号。
最后递归后序遍历我们构建的二叉树即可
void print(node* root) {
if (root == nullptr) return;
if (root->left != nullptr) {
print(root->left);
}
if (root->right != nullptr) {
print(root->right);
}
cout << root->val;
}
完整代码
#include <bits/stdc++.h>
using namespace std;
string middle, pre;
struct node {
node(char c) : val(c) {
left = nullptr;
right = nullptr;
}
char val;
node *left, *right;
pair<int, int> leftlist, rightlist;
};
void init_node(node* cur, pair<int, int> pos) {
int key = pos.first;
while (key < pos.second && middle[key] != cur->val) {
key++;
}
cur->leftlist = {pos.first, key};
cur->rightlist = {key + 1, pos.second};
}
pair<node*, bool> find(node* root, char c) {
auto lpos = root->leftlist;
auto rpos = root->rightlist;
node* rt = nullptr;
bool rtleft;
for (int i = lpos.first; i < lpos.second; ++i) {
if (middle[i] == c) {
if (root->left != nullptr) {
auto [temp, isleft] = find(root->left, c);
if (temp != nullptr) {
rt = temp;
rtleft = isleft;
break;
}
}
rt = root;
rtleft = true;
break;
}
}
if (rt != nullptr) return {rt, rtleft};
for (int i = rpos.first; i < rpos.second; ++i) {
if (middle[i] == c) {
if (root->right != nullptr) {
auto [temp, isleft] = find(root->right, c);
if (temp != nullptr) {
rt = temp;
rtleft = isleft;
break;
}
}
rt = root;
rtleft = false;
break;
}
}
if (rt != nullptr) return {rt, rtleft};
return {nullptr, false};
}
void print(node* root) {
if (root == nullptr) return;
if (root->left != nullptr) {
print(root->left);
}
if (root->right != nullptr) {
print(root->right);
}
cout << root->val;
}
int main () {
cin >> middle >> pre;
node *root = new node(pre[0]);
init_node(root, {0, pre.size()});
for (int i = 1; i < pre.size(); ++i) {
char curchar = pre[i];
auto temp = find(root, curchar);
node* target = temp.first;
if (!target) cout << "yes empty" << endl;
bool isleft = temp.second;
if (isleft) {
target->left = new node(curchar);
init_node(target->left, target->leftlist);
}
else {
target->right = new node(curchar);
init_node(target->right, target->rightlist);
}
}
print(root);
cout << endl;
return 0;
}
方法二
递归
对于每一个子问题,如果确定了其先序遍历和中序遍历在其原数组中的区间,便可以生成当前节点并计算出左右子树的区间,具体计算如下。
当前状态,分别有preleft(先序遍历左端点索引), preright, inleft(中序遍历左节点索引), inright
对于当前节点,首先获取其在中序遍历数组的索引rootpos
用rootpos减去inleft是左子树节点的数量,同理inright减去rootpos是右子树节点的数量。
先序序列中,首个元素是当前状态的根节点
左子树的preleft为当前的preleft + 1
左子树的preright为当前的preleft + leftcount
同理可推出右子树的
字数的inleft和inright比较好求,这里依旧使用左子树举例:
inleft不变, inright为root_pos - 1
有了以上信息后即可递归的构造二叉树
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
* right(right) {}
* };
*/
class Solution {
public:
unordered_map<int, int> table;
TreeNode* dfs(vector<int>& preorder, vector<int>& inorder, int pre_left, int pre_right, int in_left, int in_right) {
if (pre_left > pre_right) return nullptr;
int root_pos = table[preorder[pre_left]];
TreeNode * root = new TreeNode(inorder[root_pos]);
int leftcount = root_pos - in_left;
int rightcount = in_right - root_pos;
TreeNode* root_left = dfs(preorder, inorder, pre_left + 1, pre_left + leftcount, in_left, root_pos - 1);
TreeNode* root_right = dfs(preorder, inorder, pre_left + leftcount + 1, pre_left + leftcount + rightcount, root_pos + 1, in_right);
root->left = root_left;
root->right = root_right;
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
for (int i = 0; i < inorder.size(); ++i) {
table[inorder[i]] = i;
}
int n = preorder.size();
TreeNode *res = dfs(preorder, inorder, 0, n - 1, 0, n - 1);
return res;
}
};