【笔记】Tree总结篇~

Tree类型题目模板总结

参考资料:
【二叉树遍历(前序、中序、后序、层次、深度优先、广度优先遍历)非常全面!】
https://www.cnblogs.com/llguanli/p/7363657.html?from=singlemessage
【102. Binary Tree Level Order Traversal-Solution】
https://leetcode.com/problems/binary-tree-level-order-traversal/solution/
在这里插入图片描述
在这里插入图片描述

树的遍历不放null节点(压入左右节点之前做null检查)的原因,是因为本来遍历就不需要输出null,所以没必要入栈!
为什么要单独的help函数呢?

二叉树是一种非常重要的数据结构,非常多其他数据结构都是基于二叉树的基础演变而来的。对于二叉树,有深度遍历和广度遍历,深度遍历有前序、中序以及后序三种遍历方法,广度遍历即我们寻常所说的层次遍历。由于树的定义本身就是递归定义,因此採用递归的方法去实现树的三种遍历不仅easy理解并且代码非常简洁,而对于广度遍历来说,须要其他数据结构的支撑。比方堆了。所以。对于一段代码来说,可读性有时候要比代码本身的效率要重要的多。

四种基本的遍历思想为:

前序遍历:根结点 —> 左子树 —> 右子树

中序遍历:左子树—> 根结点 —> 右子树

后序遍历:左子树 —> 右子树 —> 根结点

层次遍历:仅仅需按层次遍历就可以

比如。求以下二叉树的各种遍历

在这里插入图片描述

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

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

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

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

一、DFS

1. 前序遍历DFS- Preorder

[144 Binary Tree Preorder Traversal]
https://leetcode.com/problems/binary-tree-preorder-traversal/

(1) 前序遍历-递归版本

1)依据上文提到的遍历思路:根结点 —> 左子树 —> 右子树,非常easy写出递归版本号:

//递归,DFS preorder
// Recursively.
     vector<int> preorderTraversal1(TreeNode* root) {
        vector<int> res;  //一定要写一个单独的递归函数!不然会在这个函数中不停新建res,为什么这里不用new?
        if (root == NULL) return res; // 防御编程,要注意如果是null要返回vector类型的结果!!!不能返回null!!因为res是刚刚创建的,所以返回的是空res
        helper(root, res); // 另一个函数里面实现递归
        return res;   // 最后一定要记得写return!!!
    }
    
    void helper(TreeNode* root, vector<int> &res){  // 函数类型是void,要注意引用res,这个是我们要在这个函数里面改变的
        if(root == NULL) return;  //如果到叶子节点,则返回,注意是返回到上一个栈,然后继续递归或者回溯。返回的时候不用返回东西,直接return。
        res.push_back(root -> val); 
        helper(root->left, res);
        helper(root->right, res); //,然后有两个参数
    }

(2) 前序遍历-非递归版本

【用栈来模拟是因为弹出左孩子,访问右孩子的时候需要知道父亲节点是什么】
2)如今讨论非递归的版本号:【进栈前访问】
依据前序遍历的顺序,优先訪问根结点。然后在訪问左子树和右子树。所以。对于随意结点node。第一部分即直接訪问之,之后在推断左子树是否为空,不为空时即反复上面的步骤,直到其为空若为空。则须要訪问右子树。注意。在訪问过左孩子之后。须要反过来訪问其右孩子。所以,须要栈这样的数据结构的支持。对于随意一个结点node,详细过程例如以下:

a)訪问之,并把结点node入栈。当前结点置为左孩子;

b)推断结点node是否为空,若为空。则取出栈顶结点并出栈,将右孩子置为当前结点;否则反复a)步直到当前结点为空或者栈为空(能够发现栈中的结点就是为了訪问右孩子才存储的)

代码例如以下:

// 非递归写法1:
//前序遍历的递归定义:先根节点,后左子树,再右子树。
//首先,我们遍历左子树,边遍历边打印,并把根节点存入栈中,以后需借助这些节点进入右子树开启新一轮的循环。

class Solution{
public:
    vector<int> preorderTraversal(TreeNode* root){
        vector<int> res;
        if (root==NULL) return res;
        stack<TreeNode*> s;
        TreeNode* temp = root;
        while (temp || !s.empty()){
            if(temp){   // 每次都要判断有没有左孩子
                res.push_back( temp->val ); 
                s.push(temp);
                temp = temp->left; //每次会把下一次的指针置为左节点,实现移动效果
            }
            else{
            temp = s.top();
            s.pop();
            temp = temp->right;
            }
        }
        return res;
    }
};


// 非递归写法2:直接用while循环走到最左边节点
class Solution{
public:
    vector<int> preorderTraversal(TreeNode* root){
        vector<int> res;
        if (root==NULL) return res;
        stack<TreeNode*> s;
        TreeNode* temp = root;
        while (temp || !s.empty()){
            while(temp){
                res.push_back( temp->val );  // 先序遍历,边走边打印!打印完压栈!一直走到最左边
                s.push(temp);
                temp = temp->left;
            }
            temp = s.top();
            s.pop();
            temp = temp->right;
        }
        return res;
    }
};


// 非递归写法3: 和BFS相似:用栈来模拟
/// **和BFS相似:先操作,然后压其右->左节点**
/// **不同的是,BFS用队列,DFS用栈**
/// 此时栈顶是左孩子,则弹出,继续压入左孩子的右、左孩子
   vector<int> preorderTraversal(TreeNode *root){
       vector<int> res;
       if(root == NULL) return res;  // 前面两行是一样的
       stack<TreeNode*> preStack; //新建一个stack,模拟递归
       preStack.push(root);  //首先是的root先入栈
       TreeNode *temp =root;  //设置一个temp变量,用来放接下来产生current node
       while( !preStack.empty()){
       	// preorder operation,弹出当前的栈顶元素
           temp = preStack.top();
           preStack.pop();
           res.push_back(temp->val);
           
           // 要注意是先right入栈然后再是left
           // 把它的左右子节点放进去!// 判断是否入栈,是判断是否为空。不用加非号(!)
           if( temp->right ) preStack.push( temp->right );  
           if( temp->left ) preStack.push( temp->left );
       }
       return res;
   }

DFS和BFS
相似:先操作,然后压左右节点
不同:BFS用队列,DFS用栈

2. 中序遍历-DFS- Inorder

[94 Binary Tree Inorder Traversal]
https://leetcode.com/problems/binary-tree-inorder-traversal/

(1) 中序遍历-递归版本

1)依据上文提到的遍历思路:左子树 —> 根结点 —> 右子树,非常easy写出递归版本号:

    vector<int> inorderTraversal1(TreeNode* root) {
        vector<int> res;  // 不能写在一起的原因是每次递归要新建一个res,最后的结果是在不同的栈区
        if( root == NULL ) return res;
        helper(root, res);
        return res;
    }
    
    void helper(TreeNode* root, vector<int> &res){
        if( root == NULL) return;
        if( root->left ) helper( root->left, res);
        res.push_back( root->val );
        if( root->right ) helper( root->right, res);
    }
    

(2) 中序遍历-非递归版本

非递归实现,有了上面前序的解释,中序也就比較简单了。同样的道理。仅仅只是訪问的顺序移到出栈时。代码例如以下:

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        if( root == NULL ) return res;
        stack<TreeNode*> s;
        
        TreeNode* temp = root;
        while(temp || !s.empty()){
            // 走到最左边
            while(temp)
            {
                s.push(temp);
                temp = temp->left;   // 要记得移动指针!
            }
            // 然后弹出当前栈顶,也就是最左边的点,然后把当前节点的最节点放进栈里面,要是没有右节点,会从栈里弹出一个栈顶节点,也是当前节点的父节点或者祖父节点哈哈哈
            temp = s.top();
            s.pop();
            res.push_back(temp->val);  //中序遍历,边走边打印!打印完压栈!一直走到最左边
            temp = temp->right;
        }
        
        return res;
    }

3. 后序遍历-DFS- postorder

[145 Binary Tree Postorder Traversal]
https://leetcode.com/problems/binary-tree-postorder-traversal/

(1) 后序遍历-递归版本

1)依据上文提到的遍历思路:左子树 —> 右子树 —> 根结点。非常easy写出递归版本号:

    vector<int> postorderTraversal1(TreeNode* root) {
        vector<int> res;
        if (root == NULL) return res;
        helper(root, res);
        return res;
    }
    
    void helper(TreeNode* root, vector<int>& res){
        if (root == NULL) return;
        if (root->left) helper(root->left, res);
        if (root->right) helper(root->right, res);
        res.push_back(root->val);
    }

(2) 后序遍历-非递归版本

后序遍历的非递归实现是三种遍历方式中最难的一种。由于在后序遍历中,要保证左孩子和右孩子都已被訪问而且左孩子在右孩子前訪问才干訪问根结点,这就为流程的控制带来了难题。以下介绍两种思路。

第一种思路:对于任一结点P,将其入栈,然后沿其左子树一直往下搜索。直到搜索到没有左孩子的结点,此时该结点出如今栈顶,可是此时不能将其出栈并訪问,因此其右孩子还为被訪问。

所以接下来依照同样的规则对其右子树进行同样的处理,当訪问完其右孩子时。该结点又出如今栈顶,此时能够将其出栈并訪问。这样就保证了正确的訪问顺序。能够看出,在这个过程中,每一个结点都两次出如今栈顶,仅仅有在第二次出如今栈顶时,才干訪问它。因此须要多设置一个变量标识该结点是否是第一次出如今栈顶。

// 其实也是类似的,就是多了一步判断和加标签罢了
	//后序遍历递归定义:先左子树,后右子树,再根节点。
	//后序遍历的难点在于:需要判断上次访问的节点是位于左子树,还是右子树。若是位于左子树,则需跳过根节点(也就是要把的根节点第二次压入!||或者不弹出就不用重新压入了),先进入右子树,再回头访问根节点;若是位于右子树,则直接访问根节点。
	//后序方法1:
	    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        if (root == NULL) return res;
        stack<TreeNode*> postStack;
        
        TreeNode* temp = root;
        TreeNode* visited = NULL;
        
        while( temp || !postStack.empty()){
        	//先访问左节点
            while(temp){  // 也是沿左子树一直往下搜索,到最左
                postStack.push(temp);
                temp = temp->left;
            }
            temp = postStack.top();
            //再访问右节点
            if ( temp->right && temp->right != visited ){  //逆否命题厉害了!取出一个节点,要记得判断是否有右节点,且,右节点是否被访问过了。  【如果右孩子不为空且未被访问过】
                temp = temp->right;
            }
            else{  //满足右节点被访问过了或者没有右孩子,就可以弹出当前节点
                postStack.pop();
                res.push_back(temp->val);
                visited = temp;   //要记得记录已经访问的上一个节点,所以到这个节点的根节点时,就可以通过对比根节点的右孩子和上次访问的节点来判断是否访问过!
                temp = NULL; //最后要记得把temp置空,然后否则指针在这里会又一次入栈!!这是重点!
            }
        }
        return res;
    }

【先序:进栈时访问。中序:(第一次)出栈时访问。后序:第二次出栈时/真正出栈时访问】

DFS总结

事实上深度遍历就是上面的前序、中序和后序。可是为了保证与广度优先遍历相照顾,也写在这。代码也比較好理解,事实上就是前序遍历,代码例如以下:

// java 代码
public void depthOrderTraverse(TreeNode root) {  
        if (root == null) {  
            return;  
        }  
        LinkedList<TreeNode> stack = new LinkedList<>();  
        stack.push(root);  
        while (!stack.isEmpty()) {  
            TreeNode node = stack.pop();  
            System.out.print(node.val+"  ");  
            if (node.right != null) {  
                stack.push(node.right);  
            }  
            if (node.left != null) {  
                stack.push(node.left);  
            }  
        }  
    }  

二、BFS

BFS level-order

层次遍历的代码比較简单。仅仅须要一个队列就可以。先在队列中增加根结点。之后对于随意一个结点来说。在其出队列的时候,訪问之。同一时候假设左孩子和右孩子有不为空的。入队列。代码例如以下:
[102. Binary Tree Level Order Traversal]
https://leetcode.com/problems/binary-tree-level-order-traversal/

// 用队列deque实现BFS
    vector<vector<int>> BFSlevelOrder(TreeNode* root) {
        vector<vector<int> > res;
        if(root == NULL) return res;
        
        vector<int> cur;
        deque<TreeNode*> q;
        TreeNode* temp = root;
        
        q.push_back(root);
        q.push_back(NULL);
        while(q.size()){
            temp = q.front();
            q.pop_front();
            // if(temp == NULL && q.front() != NULL){  //最后的那个NULL是要特殊判断,但是不能用这个判,因为最后的NULL不满足这个条件,就会进入else也就是继续入队,然后就会TE。
            if(temp == NULL){    
                res.push_back(cur);
                cur.resize(0);
                if(q.size()){   //如果是最后一个元素了.但是不能用q.front()来判断,在空容器上对 front 的调用是未定义的。you need to check that the container contains something using empty() (which checks if the container is empty) before calling front().
                    q.push_back(NULL);
                }
            }   
            else{
                cur.push_back(temp->val);
                if(temp->left) q.push_back(temp->left);
                if(temp->right) q.push_back(temp->right);
            }  
        }
        return res; 
    }


// 102 题,BFS分行打印,加了NULL的判断
    vector<vector<int>> BFSlevelOrder(TreeNode* root) {
        vector<vector<int> > res;
        if(root == NULL) return res;
        
        vector<int> cur;
        deque<TreeNode*> q;
        TreeNode* temp = root;
        
        q.push_back(root);
        q.push_back(NULL);
        while(q.size()){
            temp = q.front();
            q.pop_front();
            // if(temp == NULL && q.front() != NULL){  //最后的那个NULL是要特殊判断,但是不能用这个判,因为最后的NULL不满足这个条件,就会进入else也就是继续入队,然后就会TE。
            if(temp == NULL){    
                res.push_back(cur);
                cur.resize(0);
                if(q.size()){   //如果是最后一个元素了.但是不能用q.front()来判断,在空容器上对 front 的调用是未定义的。you need to check that the container contains something using empty() (which checks if the container is empty) before calling front().
                    q.push_back(NULL);
                }
            }   
            else{
                cur.push_back(temp->val);
                if(temp->left) q.push_back(temp->left);
                if(temp->right) q.push_back(temp->right);
            }  
        }
        return res; 
    }


DFS 实现 BFS

    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int> > res;
        if(root == NULL) return res;
        int level = 0;
        DFS(root, res, level);
        return res;
     }
    
    // 重点在于二维数组的操作与理解,**重点是用level控制递归的结果放入哪个level的vector中**
    //相当于多个vector又拼成了一串vector,操作对应的子vector要用下标索引!
    //下标是从0开始的,所以层level的对应也是从0开始
    void DFS(TreeNode* root, vector<vector<int> > &res, int level){
        if(root == NULL) return;
        if(level == res.size()) res.push_back(vector<int>());  //每一层(level)创建一个vector存节点;第一次进入该层的时候创建,之后的就是如果同层就加入到对应的vector!
        res[level].push_back(root->val); // 往对应的level里面放当前层的节点
        if(root->left) DFS(root->left, res, level+1);
        if(root->right) DFS(root->right, res, level+1);
    } 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值