• 二叉树总结(二)树的遍历


    该文我会用来总结二叉树相关的知识

    二叉树如下图:

    二叉树的结构

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

    二叉树构造方法

    为了测试二叉树的的各种算法,我不得不写一个二叉树的构造方法,我主要是用层次遍历的方式来构造二叉树的。层次遍历在后面会详细说到。

    用字符串的方式来输入二叉树的序列,例如:

    输入:1 2 3 4 5 6 7 null null null null null 8 null null

    以上输入都是字符,最后两个null可省略。

    TreeNode *createBinaryTree(vector<string> &arr){
        TreeNode *head = NULL;
        if (!arr.at(0).compare("null"))return head;//空树
        queue<TreeNode *>Q;
        vector<string>::iterator it = arr.begin();
        head = new TreeNode(stringToInteger(*it++));//stringToInteger将string转为int
        TreeNode *p = NULL;
        Q.push(head);//树根入队
        //队列不空,且没超过arr的大小(用于arr最后多余的null未列出来的情况,即上面说的可省略的情况)
        while (!Q.empty() && it != arr.end()){
            p = Q.front();//取出队首元素
            Q.pop();
            if (!(*it).compare("null")){//arr序列的下一个为空,则左子树为空
                p->left = NULL;
            }
            else{//否则生成左子树
                p->left = new TreeNode(stringToInteger(*it));
                Q.push(p->left);//左子树入队
            }
            ++it;
            if (it == arr.end())break;//不忘判断arr是否越界
            if (!(*it).compare("null")){//arr序列的下一个为空,则右子树为空
                p->right = NULL;
            }
            else{//否则生成右子树
                p->right = new TreeNode(stringToInteger(*it));
                Q.push(p->right);//右子树入队
            }
            ++it;
        }
        return head;
    }

    用数值的方式来输入二叉树的序列,例如:

    输入:1 2 3 4 5 6 7 -1 -1 -1 -1 -1 8

    默认树的所有值都是大于0的。

    TreeNode *createBinaryTree(const vector<int> &arr){
        int index = 0;
        queue<TreeNode *>Q;
        TreeNode *head = new TreeNode(arr.at(index++));
        Q.push(head);
        TreeNode *p = NULL;
        while (index < arr.size() && !Q.empty()){
            p = Q.front();
            Q.pop();
            if (arr.at(index) >= 0){
                TreeNode *temp = new TreeNode(arr.at(index));
                p->left = temp;
                Q.push(temp);
            }
            ++index;
            if (index >= arr.size())break;
            if (arr.at(index) >= 0){
                TreeNode *temp = new TreeNode(arr.at(index));
                p->right = temp;
                Q.push(temp);
            }
            ++index;
        }
        return head;
    }

    二叉树遍历方法

    前序遍历:1->2->4->5->3->6->8->7

    中序遍历:4->2->5->1->8->6->3->7

    后序遍历:4->5->2->8->6->7->3->1

    层次遍历:1->2->3->4->5->6->7->8

    前序递归实现:

    vector<int> preorder(TreeNode* root){
        vector<int> retInt;
        if (root == NULL)return retInt;//空树
        retInt.push_back(root->val);//先访问值
        vector<int> left = preorder(root->left);//进入左子树
        retInt.insert(retInt.end(), left.begin(), left.end());//复制左子树的访问结果
        vector<int> right = preorder(root->right);//进入右子树
        retInt.insert(retInt.end(), right.begin(), right.end());
        return retInt;
    }

    前序费递归实现:

    vector<int> preorder(TreeNode* root){
        vector<int>retInt;
        if (root == NULL)return retInt;//空树
        stack<TreeNode *>s;
        s.push(root);//树根入栈
        while (!s.empty()){
            TreeNode *p = s.top();
            retInt.push_back(p->val);//先访问值
            if (p->left != NULL){
                s.push(p->left);//遍历左子树,左子树入栈
            }
            else{//左子树为空
                s.pop();//当前节点出栈
                while (p->right == NULL && !s.empty()){//寻找非空右子树
                    p = s.top();
                    s.pop();
                }
                if (p->right != NULL)s.push(p->right);//右节点入栈
            }
        }
        return retInt;
    }

    中序递归实现:

    vector<int> inorder(TreeNode* root){
        vector<int> retInt;
        if (root == NULL)return retInt;//空树
        retInt = inorder(root->left);//遍历左子树
        retInt.push_back(root->val);//访问当前节点值
        vector<int> temp = inorder(root->right);//遍历右子树
        retInt.insert(retInt.end(),temp.begin(),temp.end());//复制右子树的结果
        return retInt;
    }

    中序非递归实现:

    vector<int> inorder(TreeNode* root){
        vector<int> retInt;
        if (root == NULL) return retInt;//空树
        TreeNode *p = NULL;
        stack<TreeNode *> s;
        s.push(root);//树根入栈
        while (!s.empty()){
            p = s.top();//获取栈顶元素
            if (p->left != NULL){
                s.push(p->left);//其左子树入栈
            }
            else{//左子树为空时
                s.pop();
                retInt.push_back(p->val);//访问其节点值
                while (p->right == NULL && !s.empty()){//寻找非空右子树
                    p = s.top();//若右子树为空,获取新的栈顶元素
                    retInt.push_back(p->val);//访问新元素的值
                    s.pop();
                }
                if (p->right != NULL)s.push(p->right);//右子树入栈
            }
        }
        return retInt;
    }

    后序的递归实现:

    vector<int> postorderTraversal2(TreeNode* root){
        vector<int> retInt;
        if (root == NULL)return retInt;//空树
        retInt = preorderTraversal2(root->left);//进入左子树
        vector<int> right = preorderTraversal2(root->right);//进入右子树
        retInt.insert(retInt.end(), right.begin(), right.end());
        retInt.push_back(root->val);//最后访问值
        return retInt;
    }

    后序的非递归实现:

     下面的方法需要增加数组记录已访问的所有节点,空间时间开销都比较大

    bool LeetCode::checkVisitedPost(vector<TreeNode*>& visited, TreeNode* p){
        for (vector<int>::size_type i = 0; i < visited.size(); i++){
            if (visited.at(i) == p)return true;
        }
        return false;
    }
    vector<int> LeetCode::postorderTraversal(TreeNode* root){
        vector<int> retInt;
        if (root == nullptr)return retInt;//空树
        stack<TreeNode *>s;
        vector<TreeNode *>visited;//是否已访问过
        s.push(root);
        visited.push_back(root);
        while (!s.empty()){
            TreeNode* p = s.top();
            if (p->left && !checkVisitedPost(visited,p->left)){//左节点存在,且未访问过
                s.push(p->left);
                visited.push_back(p->left);
            }
            else if (p->right && !checkVisitedPost(visited, p->right)){//右节点存在,且未访问过
                s.push(p->right);
                visited.push_back(p->right);
            }
            else{
                retInt.push_back(p->val);//访问该节点
                s.pop();
            }
        }
        return retInt;
    }

     下面的方法避开存储所有的已访问节点,而只存最后访问的右节点;时间空间开销都好很多;

    vector<int> LeetCode::postorderTraversal(TreeNode* root){
        vector<int> retInt;
        if (root == nullptr)return retInt;//空树
        stack<TreeNode *>s;
        TreeNode* visited = nullptr;//记录前面已访问的树节点
        TreeNode* p = root;//记录当前节点
        while (p || !s.empty()){//!p保证根节点能入栈
            while (p){//左子树入栈
                s.push(p);
                p = p->left;
            }
            p = s.top();//获得最后的左节点
            if (!p->right || p->right == visited){
                retInt.push_back(p->val);//左右子树都已访问或为空,则访问当前节点
                visited = p;//记录已访问的右节点
                s.pop();
                p = nullptr;//置为空,防止重复遍历左子树
            }
            else p = p->right;
        }
        return retInt;
    }

    层次遍历的非递归实现:

    使用队列保存每层的节点,另外为了知道每层的节点数量而增加了num数组。将每一层看做一个等级,每次要将遍历的节点的孩子节点加到对应的等级的数量中去。

    vector<vector<int>> levelOrder(TreeNode* root){
        vector<vector<int>> ret;
        if (root == NULL)return ret;
        queue<TreeNode *>Q;
        Q.push(root);
        //表示当前的层次
        vector<int>::size_type level = 0;
        //当前层次的节点数量
        vector<int> num;
        num.push_back(1);
        while (!Q.empty()){
            TreeNode *p = Q.front();
            //判断当前等级对应的数组是否创建
            if (level < ret.size()){//已创建
                ret.at(level).push_back(p->val);
            }
            else{//未创建
                vector<int>nodei;
                nodei.push_back(p->val);
                ret.push_back(nodei);
            }
            Q.pop();
            --num.at(level);//数量减一
            if (p->left != NULL){//左子树入队
                Q.push(p->left);
                //判断当前等级对应的数量是否存在
                if (level < num.size() - 1){//已存在
                    num.at(level + 1)++;
                }
                else{//不存在
                    num.push_back(1);
                }
            }
            if (p->right != NULL){//右子树入队
                Q.push(p->right);
                if (level < num.size() - 1){
                    num.at(level + 1)++;
                }
                else{
                    num.push_back(1);
                }
            }
            if (num.at(level) <= 0){//当前层次等级的数量没有了,则进入下一层
                level++;
            }
        }
        return ret;
    }

    遍历的应用:

    下面按照LeetCode中的题目来说明遍历的一些应用:

    94 Binary Tree Inorder Traversal可用中序遍历来检查,二叉搜索树的中序遍历结果是递增的,这个按照上面的遍历算法就能简单解决。

    100 Same Tree 判断两颗二叉树是否相同。遍历一遍,比较每个节点就可以了。

    bool isSameTree(TreeNode* p, TreeNode* q){
        if (!p && !q)return true;//空树判断
        else if (!p || !q)return false;
        if (p->val != p->val)return false;//值比较
        return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
    }

    101 Symmetric Tree 判断一颗二叉树是否镜像对称。两个指针按照先序遍历,一个按照根、左儿子、右儿子,一个按照根、右儿子、左儿子的顺序遍历,然后比较每个节点就可以了。注意,当遍历到根节点时,就可以停止,不需要遍历完整颗树。

    递归的做法:

    bool isSymmetric(TreeNode* root){
        if (!root)return true;
        return isSymmetricRecur(root->left, root->right);
    }
    
    bool isSymmetricRecur(TreeNode* left, TreeNode*right){
        if (!left && !right)return true;
        else if (!left || !right)return false;
        if (left->val != right->val)return false;
        return isSymmetricRecur(left->left, right->right) && isSymmetricRecur(left->right, right->left);
    }

    非递归的做法:

    bool isSymmetric(TreeNode* root){
        if (!root)return true;
        TreeNode *p = root->left, *q = root->right;
        stack<pair<TreeNode *, TreeNode *>>S;
        while (!S.empty() || p || q){//注意:循环条件是!S.empty() || p || q,因为q、p可能为空
            if ((!p && q)|| (!q && p))return false;//仅其中一个为空
            if (p && q){
                if (p->val != q->val)return false;//值不等,放到里面不用判断p,q是否为空
                S.push({ p, q });
                p = p->left;
                q = q->right;
            }
            else{
                p = S.top().first;
                q = S.top().second;
                S.pop();
                if (p == q)break;//树根
                p = p->right;
                q = q->left;
            }
        }
        return true;
    }

    102 Binary Tree Level Order Traversal 输出二叉树的层次遍历的结果,要求每层的结果一个集合。上面层次遍历的算法就是这题的答案。

    103 Binary Tree Zigzag Level Order Traversal 按照"之"字形来遍历每层,输出遍历的结果,要求每层的结果一个集合。

    层次遍历的算法,注意奇数层(左->右)和偶数层(右->左)的输出方向不一样就可以了。

    vector<vector<int>> LeetCode::zigzagLevelOrder(TreeNode* root){
        vector<vector<int>> ret;
        if (root == NULL)return ret;
        queue<TreeNode *>Q;
        Q.push(root);
        //表示当前的层次
        vector<int>::size_type level = 0;
        //当前层次的节点数量
        vector<int> num;
        num.push_back(1);
        stack<int>s;
        while (!Q.empty()){
            TreeNode *p = Q.front();
            //等级为奇数时,要先入栈
            if (level % 2){
                s.push(p->val);
            }
            else{
                //判断当前等级对应的数组是否创建
                if (level < ret.size()){//已创建
                    ret.at(level).push_back(p->val);
                }
                else{//未创建
                    vector<int>nodei;
                    nodei.push_back(p->val);
                    ret.push_back(nodei);
                }
            }
            Q.pop();
            --num.at(level);//数量减一
            if (p->left != NULL){//左子树入队
                Q.push(p->left);
                //判断当前等级对应的数量是否存在
                if (level < num.size() - 1){//已存在
                    num.at(level + 1)++;
                }
                else{//不存在
                    num.push_back(1);
                }
            }
            if (p->right != NULL){//右子树入队
                Q.push(p->right);
                if (level < num.size() - 1){
                    num.at(level + 1)++;
                }
                else{
                    num.push_back(1);
                }
            }
            if (num.at(level) <= 0){//当前层次等级的数量没有了,则进入下一层
                if (level % 2){
                    if (level >= ret.size()){//若没有当前层的节点数组,则建立一个
                        vector<int>nodei;
                        nodei.push_back(s.top());
                        ret.push_back(nodei);
                        s.pop();
                    }
                    while (!s.empty()){//将栈中的元素加入到当前层次的数组中,实现反序
                        ret.at(level).push_back(s.top());
                        s.pop();
                    }
                }
                level++;
            }
        }
        return ret;
    }

     104 Maximum Depth of Binary Tree 求二叉树的深度

     递归解法:

    int maxDepthRecur(TreeNode* root){
        if (!root)return 0;
        return 1 + max(maxDepthRecur(root->left), maxDepthRecur(root->right));
    }

    非递归解法

    int maxDepth(TreeNode* root){
        if (!root)return 0;
        int dep = 0, res = 0;
        TreeNode *p = root;
        stack<pair<TreeNode *, int>>S;
        while (!S.empty() || p){
            if (p){
                ++dep;
                res = max(res, dep);
                S.push({ p, dep });
                p = p->left;
            }
            else{
                p = S.top().first;
                dep = S.top().second;
                S.pop();
                p = p->right;
            }
        }
        return res;
    }

    105 Construct Binary Tree from Preorder and Inorder Traversal 通过前序和中序序列构造二叉树

    106 Construct Binary Tree from Inorder and Postorder Traversal 通过后序和中序序列构造二叉树

    上面两题解法参考 http://www.cnblogs.com/yeqluofwupheng/p/7429781.html

    107 Binary Tree Level Order Traversal II
    110 Balanced Binary Tree
    111 Minimum Depth of Binary Tree
    112 Path Sum
    113 Path Sum II
    114 Flatten Binary Tree to Linked List
    116 Populating Next Right Pointers in Each Node
    117 Populating Next Right Pointers in Each Node II
    124 Binary Tree Maximum Path Sum
    129 Sum Root to Leaf Numbers
    144 Binary Tree Preorder Traversal
    145 Binary Tree Postorder Traversal
    156 Binary Tree Upside Down
    199 Binary Tree Right Side View
    222 Count Complete Tree Nodes
    226 Invert Binary Tree
    236 Lowest Common Ancestor of a Binary Tree
    250 Count Univalue Subtrees
    257 Binary Tree Paths
    297 Serialize and Deserialize Binary Tree
    298 Binary Tree Longest Consecutive Sequence
    337 House Robber III
    366 Find Leaves of Binary Tree
    404 Sum of Left Leaves
    437 Path Sum III
    508 Most Frequent Subtree Sum
    513 Find Bottom Left Tree Value
    515 Find Largest Value in Each Tree Row
    536 Construct Binary Tree from String
    543 Diameter of Binary Tree
    545 Boundary of Binary Tree
    549 Binary Tree Longest Consecutive Sequence II
    563 Binary Tree Tilt
    572 Subtree of Another Tree
    582 Kill Process
    606 Construct String from Binary Tree
    617 Merge Two Binary Trees
    623 Add One Row to Tree
    637 Average of Levels in Binary Tree
    652 Find Duplicate Subtrees
    654 Maximum Binary Tree
    655 Print Binary Tree
    662 Maximum Width of Binary Tree
    663 Equal Tree Partition

    LeetCode的Sum Root to Leaf Numbers可用后序遍历来解决。 

    LeetCode的Binary Tree Right Side View可用后序遍历来解决。 

  • 相关阅读:
    [Hadoop]
    [Linux]
    [Linux]
    [Hadoop]
    [Hadoop]
    vue-学习笔记-API-全局配置
    vue 风格指南-必须的
    vue 学习笔记-复用-自定义指令
    vue学习笔记-过滤器
    vue 学习笔记-复用-对象混入
  • 原文地址:https://www.cnblogs.com/yeqluofwupheng/p/6661399.html
Copyright © 2020-2023  润新知