二叉树 2. 二叉树的递归遍历

二叉树 2. 二叉树的递归遍历

144. 二叉树的前序遍历 - 力扣(LeetCode)

94. 二叉树的中序遍历 - 力扣(LeetCode)

145. 二叉树的后序遍历 - 力扣(LeetCode)

代码随想录

均为:难度 3 - 简单

摘录《代码随想录》要点:

  1. 为什么很多同学看递归算法都是“一看就会,一写就废”。

    主要是对递归不成体系,没有方法论,每次写递归算法 ,都是靠玄学来写代码,代码能不能编过都靠运气。

    本篇将介绍前后中序的递归写法,一些同学可能会感觉很简单,其实不然,我们要通过简单题目把方法论确定下来,有了方法论,后面才能应付复杂的递归。

  2. 这里帮助大家确定下来递归算法的三个要素:

    每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!

    1. 确定递归函数的参数和返回值:

      确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。

    2. 确定终止条件:

      写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出

    3. 确定单层递归的逻辑:

      确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。

  3. 以下以前序遍历为例:

    1. 确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
    void traversal(TreeNode* cur, vector<int>& vec)
    
    1. 确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
    if (cur == NULL) return;
    
    1. 确定单层递归的逻辑:前序遍历是中左右的顺序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
    vec.push_back(cur->val);    // 中
    traversal(cur->left, vec);  // 左
    traversal(cur->right, vec); // 右
    

    单层递归的逻辑就是按照中左右的顺序来处理的,这样二叉树的前序遍历,基本就写完了,再看一下完整代码:

    前序遍历:

    class Solution {
    public:
        void traversal(TreeNode* cur, vector<int>& vec) {
            if (cur == NULL) return;
            vec.push_back(cur->val);    // 中
            traversal(cur->left, vec);  // 左
            traversal(cur->right, vec); // 右
        }
        vector<int> preorderTraversal(TreeNode* root) {
            vector<int> result;
            traversal(root, result);
            return result;
        }
    };
    
  4. 那么前序遍历写出来之后,中序和后序遍历就不难理解了,代码如下:

    中序遍历:

    void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        traversal(cur->left, vec);  // 左
        vec.push_back(cur->val);    // 中
        traversal(cur->right, vec); // 右
    }
    

    后序遍历:

    void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右
        vec.push_back(cur->val);    // 中
    }
    
  • 二叉树属于是本科没有深入好好学习的内容了,说实话看完上面的递归,自己写起来还是一头雾水,每个都练一次吧。

  • 前序遍历递归写法:

    class Solution:
        def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
            if not root: # 终止条件
                return []
            left = root.left
            right = root.right
            preorder_list = [root.val] + self.preorderTraversal(left) # 左
            preorder_list = preorder_list + self.preorderTraversal(right) # 右
    
            return preorder_list
    

    学习代码随想录知,还有一种写法可以更加容易理解一些:

    class Solution:
        def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
            preorder_list = []
    
            def recursion(root):
                if not root:
                    return
                preorder_list.append(root.val)
                recursion(root.left)
                recursion(root.right)
                return preorder_list
    
            recursion(root)
    
            return preorder_list
    
  • 中序遍历递归写法

    class Solution:
        def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
            if not root:
                return []
    
            inorder_list = self.inorderTraversal(root.left)
            inorder_list += [root.val]
            inorder_list += self.inorderTraversal(root.right)
    
            return inorder_list
    

    学习代码随想录知,还有一种写法可以更加容易理解一些:

    class Solution:
        def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
            inorder_list = []
    
            def recursion(root):
                if not root:
                    return
    
                recursion(root.left)
                inorder_list.append(root.val)
                recursion(root.right)
    
            recursion(root)
            return inorder_list
    
  • 后序遍历递归写法

    class Solution:
        def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
            if not root:
                return []
    
            postorder_list = self.postorderTraversal(root.left)
            postorder_list += self. postorderTraversal(root.right)
            postorder_list += [root.val]
    
            return postorder_list
    

    学习代码随想录知,还有一种写法可以更加容易理解一些:

    class Solution:
        def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
            postorder_list = []
    
            def recursion(root):
                if not root:
                    return
    
                recursion(root.left)
                recursion(root.right)
                postorder_list.append(root.val)
    
            recursion(root)
            return postorder_list
    

    这样写也可以,就是会形式上麻烦点:

    class Solution:
        def recursion(self, root, postorder_list):
            if not root:
                return
    
            self.recursion(root.left, postorder_list)
            self.recursion(root.right, postorder_list)
            postorder_list.append(root.val)
    
        def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
            postorder_list = []
    
            self.recursion(root, postorder_list)
            return postorder_list
    
  • 三种递归遍历:

    空间复杂度:

    • O(n),因为与系统堆栈有关,系统栈需要记住每个节点的值。

    时间复杂度为O(n),两种解释:

    • 方法 1:直接计数法

      每个节点只被访问一次,每次访问时间 O(1)。

      共有 n 个节点,所以总时间复杂度为:
      O(n)

    • 方法 2:递归公式 T(n) = 2T(n/2) + O(1)

      公式的意义:

      遍历 n 个节点,需递归访问 左子树(T(n/2)) 和 右子树(T(n/2))。

      访问当前节点的操作是 O(1),所以递归公式:
      T(n) = 2T(n/2) + O(1)

      递归展开:
      T(n) = 2T(n/2) + O(1) = 4T(n/4) + O(n) = 8T(n/8) + O(n) + O(n)

      直到 T(1) = O(1),递归深度为 log n,展开求和:
      O(1) + O(2) + O(4) + ... + O(n) = O(n)

    • 其中,递归深度为 log n 是因为:

      假设二叉树的节点数是 n,递归调用次数 d,

      则递归调用过程如下: n → n/2 → n/4 → n/8 → ... → 1

      该过程可以表示为: n / 2^d = 1

      两边取对数: d = log_2(n)

      递归深度 d = log_2(n),即二叉树的高度 h。

你可能感兴趣的:(小白的代码随想录刷题笔记,Mophead的小白刷题笔记,leetcode,python,二叉树,代码随想录)