• 全面剖析【二叉树】的各类遍历方法


    二叉树遍历

    二叉树的遍历主要有四种:

    前序、中序、后序和层序
    

    遍历的实现方式主要是:

    递归和非递归
    

    递归遍历的实现非常容易,非递归的实现需要用到栈,难度系数要高一点。

    1.二叉树节点的定义

    二叉树的每个节点由节点值、左子树和右子树组成。

    class TreeNode{
    public:
        int val;
        TreeNode* left;
        TreeNode* right;
        TreeNode(int x) : val(x), left(NULL), right(NULL) {}
    }

    2.二叉树的遍历方式

    前序遍历:先访问根节点,再访问左子树,最后访问右子树

    中序遍历:先访问左子树,再访问根节点,最后访问右子树

    后序遍历:先访问左子树,再访问右子树,最后访问根节点

    层序遍历:每一层从左到右访问每一个节点。

    举例说明:(以下面的二叉树来说明这四种遍历)

    这里写图片描述

    前序遍历:ABDFGHIEC
    中序遍历:FDHGIBEAC
    后序遍历:FHIGDEBCA
    层序遍历:ABCDEFGHI

    3.前序遍历

    递归版本

    按照遍历的
    顺序很容易就能写出下列代码:

    以下代码均在leetcode测试通过,二叉树前序遍历的原题链接:戳我!leetcode直通车!上车啦!

    vector<int> preorderTraversal(TreeNode* root){
        vector<int> ret;
        dfsPreOrder(root,ret);
        return ret;
    }
    void dfsPreOrder(TreeNode* root,vector<int> &ret){
        if(root==NULL) return;
        ret.push_back(root->val);//存储根节点
        if(root->left!=NULL) dfsPreOrder(root->left,ret);//访问左子树
        if(root->right!=NULL) dfsPreOrder(root->right,ret);//访问右子树
    }

    非递归版本

    非递归版本需要利用辅助栈来实现

    1.首先把根节点压入栈中
    2.此时栈顶元素即为当前根节点,弹出并访问即可
    3.把当前根节点的右子树和左子树分别入栈,考虑到栈是先进后出,所以必须右子树先入栈,左子树后入栈
    4.重复2,3步骤,直到栈为空为止

    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ret;
        if (root==NULL) return ret;
        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty())
        {
            TreeNode* tp = st.top();//取出栈顶元素
            st.pop();
            ret.push_back(tp->val);//先访问根节点
            if(tp->right!=NULL) st.push(tp->right);//由于栈时先进后出,考虑到访问顺序,先将右子树压栈
            if(tp->left!=NULL) st.push(tp->left);//将左子树压栈
        }
        return ret;
    }

    4.中序遍历

    递归版本

    中序遍历的访问顺序依次是左子树->根节点->右子树,按照递归的思想依次访问即可

    以下代码均在leetcode测试通过,二叉树中序遍历的原题链接:戳我!leetcode直通车!上车啦!

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ret;
        inorder(root,ret);
        return ret;
    }
    void inorder(TreeNode* p,vector<int>& ret)
    {
        if(p==NULL) return;
        inorder(p->left,ret);//访问左子树
        ret.push_back(p->val);//访问根节点
        inorder(p->right,ret);//访问右子树
    }

    非递归版本

    中序遍历的非递归版本比前序稍微复杂一点,除了用到辅助栈之外,还需要一个指针p指向下一个待访问的节点

    如果p非空,则将p入栈,p指向p的左子树
    如果p为空,说明此时左子树已经访问到尽头了,弹出当前栈顶元素,进行访问,并把p设置成p的右子树的左子树,即下一个待访问的节点

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ret;
        TreeNode* p = root;
        stack<TreeNode*> st;
        while(!st.empty()||p!=NULL){
            if(p){//p非空,代表还有左子树,继续
                st.push(p);
                p=p->left;
            }
            else{//如果为空,代表左子树已经走到尽头了
                p = st.top();
                st.pop();
                ret.push_back(p->val);//访问栈顶元素
                if(p->right) {
                    st.push(p->right);//如果存在右子树,将右子树入栈
                    p = p->right->left;//p始终为下一个待访问的节点
                }
                else p=NULL;
            }
        }
        return ret;
    }

    5.后序遍历

    递归版本

    递归版本还是一样,按照访问顺序来写代码即可。

    以下代码均在leetcode测试通过,二叉树后序遍历的原题链接:戳我!leetcode直通车!上车啦!

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ret;
        inorder(root,ret);
        return ret;
     }
    void inorder(TreeNode* p,vector<int>& ret)
    {
        if(p==NULL) return;
        inorder(p->left,ret);//访问左子树
        inorder(p->right,ret);//访问右子树
        ret.push_back(p->val);//访问根节点
    }

    非递归版本

    采用一个辅助栈和两个指针p和r,p代表下一个需要访问的节点,r代表上一次需要访问的节点

    1、如果p非空,则将p入栈,p指向p的左子树

    2、如果p为空,代表左子树到了尽头,此时判断栈顶元素

    如果栈顶元素存在右子树且没有被访问过(等于r代表被访问过),则右子树入栈,p指向右子树的左子树
    如果栈顶元素不存在或者已经被访问过,则弹出栈顶元素,访问,然后p置为null,r记录上一次访问的节点p

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ret;
        TreeNode* p = root;
        stack<TreeNode*> st;
        TreeNode* r = NULL;
        while(p||!st.empty())
        {
            if(p)
            {
                st.push(p);
                p = p -> left;
            }
            else
            {
                p = st.top();
                if(p->right&&p->right!=r)
                {
                    p = p->right;
                    st.push(p);
                    p = p->left;
                }
                else 
                {
                    p = st.top();
                    st.pop();
                    ret.push_back(p->val);
                    r= p;
                    p = NULL;
                }
            }
        }
        return ret;
    }

    还有另一种解法,大家可以看看前序遍历的非递归版本,访问顺序依次是根节点->左子树->右子树,如果将压栈顺序改动一下,可以很容易得到根节点->右子树->左子树,观察这个顺序和后序遍历左子树->右子树->根节点正好反序。

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ret;
        if(root==NULL) return ret;
        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty())
        {
           TreeNode* tmp = st.top();
           ret.push_back(tmp->val);//先访问根节点
           st.pop();
           if(tmp->left!=NULL) st.push(tmp->left);//再访问左子树
           if(tmp->right!=NULL) st.push(tmp->right);//最后访问右子树
        }
        reverse(ret.begin(),ret.end());//将结果反序输出
        return ret;
    }

    6.层序遍历

    层序遍历,即按层序从左到右输出二叉树的每个节点。如例子中的A(第一层)BC(第二层)DE(第三层)FG(第四层)HI(第五层)

    层序遍历需要借助队列queue来完成,因为要满足先进先去的访问顺序。具体思路看代码:

    以下代码均在leetcode测试通过,二叉树层序遍历的原题链接:戳我!leetcode直通车!上车啦!

    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ret;
        if(root==NULL) return ret;
        queue<TreeNode*> que;
        que.push(root);
        while(!que.empty())
        {
            vector<int> temp;
            queue<TreeNode*> tmpQue;//存储下一层需要访问的节点
            while(!que.empty())//从左到右依次访问本层
            {
                TreeNode* tempNode = que.front();
                que.pop();
                temp.push_back(tempNode->val);
                if(tempNode->left!=NULL) tmpQue.push(tempNode->left);//左子树压入队列
                if(tempNode->right!=NULL) tmpQue.push(tempNode->right);//右子树压入队列
            }
            ret.push_back(temp);
            que=tmpQue;//访问下一层
        }
        return ret;
    }

    7.其他经典考题

    根据前序和中序遍历来构造二叉树

    前序遍历的顺序是:根节点->左子树->右子树,中序遍历的顺序时:左子树->根节点->右子树。

    在前序遍历中第一个节点为根节点,然后去中序遍历中找到根节点,则其左边为左子树,右边为右子树

    例如前序遍历ABC,中序遍历BAC,在前序遍历中找到根节点A,在中序遍历中A的左边B为左子树,右边C为右子树。

    然后一次递归下去,就可以把整棵数构造出来了。

    以下代码均在leetcode测试通过,构造二叉树的原题链接:戳我!leetcode直通车!上车啦!

    typedef vector<int>::iterator vi;
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if(preorder.empty()||inorder.empty()) return (TreeNode*)NULL;
        vi preStart = preorder.begin();
        vi preEnd = preorder.end()-1;
        vi inStart = inorder.begin();
        vi inEnd = inorder.end()-1;
        return constructTree(preStart,preEnd,inStart,inEnd);
    }
    TreeNode* constructTree(vi preStart,vi preEnd,vi inStart,vi inEnd)
    {
        if(preStart>preEnd||inStart>inEnd) return NULL;
        //前序遍历的第一个节点为根节点
        TreeNode* root = new TreeNode(*preStart);
        if(preStart==preEnd||inStart==inEnd) return root;
        vi rootIn = inStart;
        while(rootIn!=inEnd){//在中序遍历中找到根节点
            if(*rootIn==*preStart) break;
            else ++rootIn;
        }
        root->left = constructTree(preStart+1,preStart+(rootIn-inStart),inStart,rootIn-1);//递归构造左子树
        root->right = constructTree(preStart+(rootIn-inStart)+1,preEnd,rootIn+1,inEnd);//递归构造右子树
        return root;
    } 

    根据中序和后序遍历构造二叉树

    与上面的题目比较相似,后序遍历中最后一个节点为根节点,然后在中序遍历中找到根节点,左边为左子树,右边为右子树。

    以下代码均在leetcode测试通过,构造二叉树的原题链接:戳我!leetcode直通车!上车啦!

    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if(inorder.empty()||postorder.empty()) return NULL;
        return constructTree(inorder,postorder,0,inorder.size()-1,0,postorder.size()-1);
    }
    TreeNode* constructTree(vector<int>& inorder, vector<int>& postorder, int inStart,int inEnd,int postStart,int postEnd)
    {
        if(postStart>postEnd||inStart>inEnd) return NULL;
        TreeNode* root = new TreeNode(postorder[postEnd]);
        if(postStart==postEnd||inStart==inEnd) return root;
        int i ;
        for(i = inStart ;i<inEnd;i++)//在中序遍历中找到根节点
        {
            if(inorder[i]==postorder[postEnd]) break;
        }
        root->left = constructTree(inorder,postorder,inStart,i-1,postStart,postStart+i-inStart-1);//递归构造左子树
        root->right = constructTree(inorder,postorder,i+1,inEnd,postStart+i-inStart,postEnd-1);//递归构造右子树
        return root;
    }

    求二叉树的深度

    采用深度优先搜索,可以很容易计算出深度

    以下代码均在leetcode测试通过,二叉树的深度的原题链接:戳我!leetcode直通车!上车啦!

    int maxDepth(TreeNode* root) {
        return DfsTree(root);
    }
    int DfsTree(TreeNode* root){
        if(root==NULL) return 0;
        int left = DfsTree(root->left);//左子树的深度
        int right = DfsTree(root->right);//右子树的深度
        return left>right?left+1:right+1;//比较左右子树的深度,取最大值
    }

    判断是否为平衡二叉树

    利用上面求深度的思想,求出左右子树的深度,判断它们相差是否大于1,如果大于则返回false。

    以下代码均在leetcode测试通过,判断平衡二叉树的原题链接:戳我!leetcode直通车!上车啦!

    bool isBalanced(TreeNode* root) {
        return dfsTree(root)!=-1;
    }
    int dfsTree(TreeNode* root)
    {
        if(root==NULL) return 0;
        int left = dfsTree(root->left);//求左子树的深度
        if(left == -1) return -1;//返回-1代表左子树不平衡
        int right = dfsTree(root->right);//求右子树的深度
        if(right== -1) return -1;//返回-1代表右子树不平衡
        if(abs(left-right)>1) return -1;//如果左右子树均平衡,则判断它们是否相差小于等于1
        return max(left,right)+1;//返回该根节点树的深度
    }

    原博客地址:http://blog.csdn.net/terence1212/article/details/52182836

  • 相关阅读:
    python项目打包成exe
    sql同比环比计算
    七款好看文字样式纯css
    一站式智能芯片定制技术
    实战清除电脑上恶意弹出广告窗口
    GAAFET与FinFET架构
    MIPI多媒体接口
    Intel GPU实现游戏与数据中心
    芯片倒爷赚钱术
    Cache Memory技术示例
  • 原文地址:https://www.cnblogs.com/TCB-Java/p/6809624.html
Copyright © 2020-2023  润新知