侧边栏壁纸
博主头像
Hope博主等级

努力赚钱的工科研究生

  • 累计撰写 362 篇文章
  • 累计创建 129 个标签
  • 累计收到 5 条评论
标签搜索

二叉树遍历终极总结版

Hope
2022-04-04 / 0 评论 / 0 点赞 / 422 阅读 / 3,714 字
温馨提示:
本文最后更新于 2022-04-05,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

二叉树遍历分为 前 中 后 遍历 + 层序遍历。

  1. 前序遍历

所谓前序遍历就是先遍历根节点,然后左儿子,然后右儿子。

image.png

前序遍历顺序为: A B D E C F G

  1. 中序遍历

所谓中序遍历就是先遍历左儿子,然后是根节点,然后是右儿子。

image.png

中序遍历顺序为:D B E A F C G

  1. 后序遍历

所谓后序遍历就是先遍历右儿子,然后是左儿子,然后是根节点。

image.png

后序遍历顺序为:D E B F G C A

总结一下前中后,就是根节点在三个点中的位置。

  1. 层序遍历

层序遍历就是从上到下,从左到右顺序遍历树。

image.png

层序遍历的顺序:A B C D E F G

前中后遍历一般都是写一个深搜去递归。

前序遍历:

void dfs(TreeNode* root){
    //根节点
    if(root) res.push_back(root->val);
    //左儿子
    if(root->left) dfs(root->left);
    //右儿子
    if(root->right) dfs(root->right);
}

中序遍历:

void dfs(TreeNode* root){
    //左儿子
    if(root->left) dfs(root->left);
    //根节点
    if(root) res.push_back(root->val);
    //右儿子
    if(root->right) dfs(root->right);
}

后序遍历:

void dfs(TreeNode* root){
    //左儿子
    if(root->left) dfs(root->left);
    //右儿子
    if(root->right) dfs(root->right);
    //根节点
    if(root) res.push_back(root->val);
}

层序遍历:

层序遍历需要开辟一个队列来维护每一层的节点。

vector<vector<int>> levelOrder(TreeNode* root) {
    //bfs
    vector<vector<int>> res;
    queue<TreeNode*> q;
    if(root) q.push(root);
    //q中每次存的是一层
    while(q.size()){
        vector<int> level;
        int len=q.size();
        while(len--){
            auto t=q.front();
            q.pop();
            level.push_back(t->val);
            //这里维护的是下一层的节点
            if(t->left) q.push(t->left);
            if(t->right) q.push(t->right);
        }
        res.push_back(level);
    }
    return res;
}

另一种比较常见的题目就是给定两个遍历的方式,然后去寻找另一种遍历方式。
这里需要注意的是,给定的两个遍历方式中,必须有中序遍历。

比如给定了前序遍历和中序遍历,构建二叉树。

image.png

在前序遍历中,第一个点是根节点,而中序遍历中,根节点的左侧是左子树的成员,根节点的右侧是右子树的成员。所以只要找好中序遍历中的位置关系可以继续递归左子树和右子树。

image.png

在中序遍历中,左侧DBE是左子树的成员,右侧FCG是右子树的成员。
而在前序遍历中时限遍历根节点,然后是左子树然后是右子树,所以各类成员都是在一起的
我们确定在中序遍历中,左子树成员的长度L,从前序遍历中,找到长度为L的为左子树的前序遍历,中序遍历中,右子树的长度为R,从前序遍历中,在左子树成员之后的为长度为R的为右子树的成员(这里只要找到起点就可以了,剩下的肯定是右子树成员)。

在编写代码之前先考虑一下,递归终止的条件。
如果前序遍历中没有元素了即终止。

代码:

unordered_map<int,int> pos;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    //二叉树的遍历 必须有中序遍历和其他两种遍历任意一个才能推出原二叉树
    //还是递归 先找到根节点
    //pos存的是中序遍历中点的下标
    for(int i=0;i<preorder.size();i++) pos[inorder[i]] = i;
    return build(preorder,inorder,0,preorder.size()-1,0,inorder.size()-1);
}

//函数返回的是树的根节点
TreeNode* build(vector<int> &preorder,vector<int> &inorder,int pl,int pr,int il,int ir){
    if(pl > pr) return NULL;
    auto root = new TreeNode(preorder[pl]);
    int k=pos[preorder[pl]];
    root->left = build(preorder , inorder , pl+1 , pl+k-il , il , k-1);
    root->right = build(preorder , inorder , pl+k-il+1 , pr , k+1 , ir);
    return root;
}

比如给定了后序遍历和中序遍历,构建二叉树。

image.png

后序遍历中最后一个点是根节点,中序遍历中根节点右侧为右子树,所以确定右子树成员FCG的长度,然后在后序遍历中FGC的长度确定了,则左子树就是最左侧到右子树上一个元素DEB。

代码:

unordered_map<int,int> pos;
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
    for(int i=0;i<inorder.size();i++) pos[inorder[i]] = i;
    return build(inorder,postorder,0,inorder.size()-1,0,postorder.size()-1);
}

TreeNode* build(vector<int> &inorder,vector<int> &postorder,int il,int ir,int pl,int pr){
    if(pl > pr) return NULL;
    auto root = new TreeNode(postorder[pr]);
    int k=pos[postorder[pr]];
    root->left = build(inorder,postorder , il , k-1 , pl , pl + k - 1 - il);
    root->right = build(inorder,postorder, k+1 , ir , pl + k -1 - il + 1 , pr-1);
    return root;
}

对中序遍历以及其它两种前序后序遍历总结:

  1. 函数返回的条件是前序或者后序的左右边界。
  2. 每次都是存储中序遍历中点的位置信息,然后寻找左子树和右子树的长度,然后递归。

比如给定了层序遍历和中序遍历,构建二叉树。

image.png

给定层序遍历,如果中序遍历中,根节点左右都有儿子且没有被使用过,则层序遍历的下一个点是根节点B的左儿子,再下一个点C是根节点的右儿子。

代码:

#include<iostream>
#include<string>
using namespace std;
string s1,s2;

void TarverseTree(int l1,int r1,int l2,int r2){
    //先找到层序遍历在中序遍历中的位置,每次递归的时候,层序遍历的第一个点是根节点
    int i,j;
    //层序遍历
    for(i = l2; i <= r2; i++){
        bool flag = false;
        //中序遍历
        for(j = l1; j <= r1; j++){
            if(s1[j] == s2[i]){
                cout<<s2[i];
                flag = true;
                break;
            }
        }
        if(flag) break;
    }
    
    if(j > l1) TarverseTree(l1, j - 1, l2, r2);
    if(j < r1) TarverseTree(j + 1, r1, l2, r2);
}
int main(){
    cin>>s1>>s2;
    
    TarverseTree(0, s1.size() - 1, 0, s2.size() - 1);
    return 0;
}

层序遍历与中序遍历组合总结:
函数每次处理的是中序遍历的边界,这一点与前序遍历 / 后序遍历 + 中序遍历不同。前者需要处理两个边界问题。

再有一类题目就是给定前序遍历找后序遍历,这种题目就不需要我们建树了,比如有如下的树。

image.png

前序遍历为: A B C
后序遍历为: B C A

如果给定的是前序遍历,只要在遍历的时候改变一下顺序,遍历完根节点之后,先遍历右子树,然后左子树。

得到如下: A C B
这时候只要将整个的顺序反转即可得到: B C A
即后序遍历。

如果给定后序遍历,只要在遍历的时候改变一下顺序,先遍历右子树,然后左子树,最后根节点。
得到如下: C B A
反转一下: A B C
即前序遍历。

0

评论区