二叉树中序先序遍历序列求后序遍历序列

中序序列:ABEDFCHG

先序序列:CBADEFGH

方法一

先序遍历每次先输出跟节点,于是我们可以根据先序序列的顺序向下构造。

中序序列先输出左子树,在输出根节点,最后是右子树,其保留了树的左右顺序结构。

我们来看一个例子:

由先序序列的首符号得知C是整颗二叉树的根节点,此时可以去查中序序列,在C左边的字符都在C的左子树当中,C右边的符号都在C的右子树当中。

于是我们可以使用以下算法先还原出二叉树:

  1. 依次从先序序列中取出字符c

  2. 查找到c的父亲,并且确定c是其左还是右child

  3. 将c添加到树中

其中最关键的地方第2步,即如何确定c的父亲是谁c是其左还是右child,下面将在程序设计中详细介绍

程序设计

为了查找c应该插入的位置,我们从根节点查起

在确定某一字符c的父亲是谁之前,我们可以先尝试确定c在当前遍历到的节点的左子树还是右子树,假设确定c在左子树中,会分为以下情况:

  1. 当前节点左为空

  2. 当前节点左不为空

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;

    }
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值