二叉树基础——遍历专题

写在前面

学习数据结构和算法,二叉树是不可能避过去的一环,而且是很重要的一环,之后的很多算法思想、编程模式其实在二叉树的相关内容中有所体现,学好二叉树,深入理解二叉树,深入理解递归思想,将会帮助我们在之后的学习中形成一个闭环,打通任督二脉。话不多说,开始学习。

二叉树基础知识整理

1、树的结构:在数据结构中,不管再复杂的结构,无非是由数组或链表组成的,树和图只是有许多分支的复杂链表。二叉树,不用说,每个结点最多有两个后续的链表。知道了是链表的结构,就不难理解树的结构。
2、树的相关概念:
根结点:顾名思义,树的第一个结点,当然有时我们会声明一个头结点用来指向根
叶子结点:树最下层的结点,没有孩子
孩子结点和双亲结点:类似链表中后继和前驱的关系
树的深度:表示有几层
度:在图中也有这个概念,一个结点的有几个孩子,就有几个度,树的度等于最大的结点度
3、树的常见表达法:
双亲表示法:每个结点包含了指向父母的指针
孩子表示法:每个结点包含了指向孩子的指针,所有孩子组成一个线性表
双亲孩子表示法:上述结合
孩子兄弟表示法:二叉树的表示方法

三种基础遍历的递归实现

前序

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
   //root->左子树->右子树 
    void help(TreeNode* root,vector<int>& res){
        if(root==NULL)
            return;
        res.push_back(root->val);
        help(root->left,res);
        help(root->right,res);
        return;
    }
    
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        help(root,res);
        return res;
    }
};

中序

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
   //左子树->root->右子树 
    void help(TreeNode* root,vector<int>& res){
        if(root==NULL)
            return;
        help(root->left,res);
        res.push_back(root->val);
        help(root->right,res);
        return;
    }
    
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        help(root,res);
        return res;
    }
};

后序

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
   //左子树->右子树->root 
    void help(TreeNode* root,vector<int>& res){
        if(root==NULL)
            return;
        help(root->left,res);
        help(root->right,res);
        res.push_back(root->val);
        return;
    }
    
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        help(root,res);
        return res;
    }
};

递归方法没有什么好讲的了,完全是按照三种遍历的定义来的。

前序、中序的迭代实现

将递归改为迭代,往往需要利用栈或者队列,就像是将dfs改为bfs。
前序

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        if(!root)
            return res;
        stack<TreeNode*> s;
        s.push(root);
        TreeNode* p=root;
        while(!s.empty()){
            p=s.top();s.pop();
            res.push_back(p->val);
            if(p->right)
                s.push(p->right);
            if(p->left)
                s.push(p->left);
        }

        return res;
    }
};

中序

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        if(!root)
            return res;
        stack<TreeNode*> s;
        TreeNode* p=root;
        //s.push(root);
        while(p || !s.empty()){
            while(p){//找到当前子树的最深左叶子结点
                s.push(p);
                p=p->left;
            }
            p=s.top();s.pop();
            res.push_back(p->val);
            p=p->right;
        }
        return res;
    }
};

层序遍历

层序遍历,顾名思义,是按照顺序对二叉树一层一层的遍历,显然就是广度优先搜索BFS。
敲黑板,背模板,BFS搭配队列结构才香。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if(!root)
            return res;
        TreeNode* p=root;
        queue<TreeNode*> q;
        q.push(root);
        //bfs核心
        int count=1;
        while(!q.empty()){
            vector<int> temp;
            int deepSize=q.size();
            while(deepSize){
                p=q.front();
                temp.push_back(p->val);
                if(p->left){
                    q.push(p->left);
                }
                if(p->right){
                    q.push(p->right);
                }
                deepSize--;
                q.pop();
            }
            res.push_back(temp);
        }
        return res;
    }
};

二叉树的最大深度

这个有点和层序遍历类似,不过用递归dfs的思想也很好理解。对每一层的左右子树都进行递归,取最大的深度为结果就可以。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(!root)
            return 0;
        int res=0;
        //bfs
        /*TreeNode* p=root;
        queue q;
        q.push(root);
        while(!q.empty()){
            int deepSize=q.size();//每层的叶子数量
            while(deepSize){
                p=q.front();
                if(p->left){
                    q.push(p->left);
                }
                if(p->right){
                    q.push(p->right);
                }
                deepSize--;
                q.pop();
            }
            res++;
        }*/

        //dfs
        res=dfs(root,res);

        return res;
    }

    int dfs(TreeNode* root,int deep){
        if(!root)
            return deep;
        deep++;
        int leftD=deep,rightD=deep;
        leftD=dfs(root->left,deep);
        rightD=dfs(root->right,deep);
        deep=max(leftD,rightD);
        return deep;
    }
};

二叉树的最大宽度

最大宽度的解法,是一种很明显的利用二叉树层序性质的题目,二叉树的层序编号结论:对于编号为n的结点,其左孩子编号为2n,右孩子编号为2n+1。二叉树某一层的宽度,等于本层最后一个结点与第一个结点的编号之差加1,利用递归的思想,深搜来解决,利用一个哈希表来记录当前深度的第一个编号。如下。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
// dfs
//遍历每一个结点,对于每一层,第一个遍历到的结点为计算当前层宽度的起始位置
//记录每一个结点的编号,pos,所在的宽度为pos-left+1
    int width=0;
    unordered_map<int,unsigned long long> book;
    void dfs(TreeNode* root,unsigned long long pos,int deep){
        if(!root)
            return;
        if(book.find(deep)==book.end())
            book[deep]=pos;
        width=width>(pos-book[deep]+1) ? width : (pos-book[deep]+1);
        dfs(root->left,2*pos,deep+1);
        dfs(root->right,2*pos+1,deep+1);   
    }

    int widthOfBinaryTree(TreeNode* root) {
        if(!root)
            return 0;
        book[1]=1;
        dfs(root,1,1);
        return width;
    }
};

根据前序和中序遍历的结果构造树

非常经典的问题,但是不知道套路的童鞋可能一时无法着手。我们先来看一看代码。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {

private:
    unordered_map<int,int> index;

public:
    TreeNode* help(vector<int>& preorder,vector<int>& inorder,
                    int left_p,int right_p,int left_i,int right_i){
        if(left_p>right_p || left_i>right_i)
            return NULL;
        
        int pre_root=left_p;//前序中的root
        int in_root=index[preorder[left_p]];//中序中的root
        int l_leftTree=in_root-left_i;//中序遍历中左子树的长度

        TreeNode *root=new TreeNode(preorder[pre_root]);
        root->left=help(preorder,inorder,left_p+1,left_p+l_leftTree,left_i,in_root-1);
        root->right=help(preorder,inorder,left_p+l_leftTree+1,right_p,in_root+1,right_i);

        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int size_p=preorder.size();
        int size_i=inorder.size();
        
        //建立哈希表
        for(int i=0;i<size_i;i++){
            index[inorder[i]]=i;
        }
        return help(preorder,inorder,0,size_p-1,0,size_i-1);
    }
};

不妨看一看递归中的最后两步,给新建的root插入左右子结点,是不是有点熟悉。返回去看一看,不就是树的前序遍历吗?凡是涉及到二叉树的题目,递归思路是真的香,因为这一类几乎都是以树的遍历方法为原型的,只是其他的步骤可能比遍历的读取较为复杂,但实际上,都是一个编程模板。接下来再看一看根据中序和后续遍历的结果构造树。

根据中序和后续遍历的结果构造树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
//中序遍历结构:左子树,根,右子树
//后序遍历结构:左子树,右子树,根

    unordered_map<int,int> book;

    TreeNode* help(const vector<int>& inorder,const vector<int>& postorder,
    int in_left,int in_right,int post_left,int post_right){
        if(post_left>post_right)
            return nullptr;
        
        //从后序遍历中获取根节点,即最后一个
        int post_root=post_right;
        //在中序遍历中定位
        int in_root=book[postorder[post_right]];
        //左子树长度
        int len_left=in_root-in_left;

        //建立头结点
        TreeNode* root=new TreeNode(postorder[post_root]);
        //建立左右子节点
        root->left=help(inorder,postorder,in_left,in_root-1,post_left,post_left+len_left-1);
        root->right=help(inorder,postorder,in_root+1,in_right,post_left+len_left,post_right-1);
        return root;
    }

    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int n=inorder.size();
        //记录中序,方便递归时查找
        for(int i=0;i<n;i++){
            book[inorder[i]]=i;
        }
        TreeNode* root=help(inorder,postorder,0,n-1,0,n-1);
        return root;
    }
};

显然,递归构造的过程也是一个前序遍历的模板扩展。

构造树小结

1、构造一棵树的问题可以分解成构造若干子树的问题,即相同子问题都在构造一棵树,因此,每一次递归向下去构造一棵子树。
2、确定遍历结果中,根结点的位置,前序遍历的结构为:根结点,左子树,右子树,中序遍历的结构为:左子树,根结点,右结点,后续遍历的结构为左子树,右子树,根结点
3、在递归函数中,先建立root,在递归去建立root的左右子树,注意左右子树在遍历结果的范围,直到越界为递归终止条件。

^ _ ^编辑不易,水平有限,大家一起学习。

你可能感兴趣的:(二叉树基础——遍历专题)