算法学习 day13 二叉树1

125 阅读5分钟

1 二叉树基础理论

1.1 二叉树种类

  • 满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。深度为k的满二叉树有2^k-1个节点。

  • 完全二叉树:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。

image.png

  • 二叉搜索树:二叉搜索树每个节点都有值,是一个有序树。满足:
    • ① 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
    • ② 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
    • ③ 它的左、右子树也分别为二叉搜索树

image.png

  • 平衡二叉搜索树,AVL(Adelson-Velsky and Landis)树: 平衡二叉搜索树首先是二叉搜索树,对于树中的任意一个节点,其左子树中的所有节点的值都小于该节点的值,而其右子树中的所有节点的值都大于该节点的值。并且满足平衡性质:对于树中的任意一个节点,其左子树和右子树的高度差(深度)的绝对值不超过1。这个条件确保了树在任何时候都保持大致平衡,从而保证了树的查找、插入和删除操作的时间复杂度都能保持在O(log n)。

1.2 二叉树的存储

分为链式存储和顺序存储。链式存储分别使用左右指针指向左右子树;顺序存储用数组存储完全二叉树,如果根节点在数组中索引为i,则左孩子和右孩子的数组下标分别为2i+12i+2

1.3 二叉树遍历

  • 深度优先遍历

    • 前序遍历(递归法,迭代法):根左右
    • 中序遍历(递归法,迭代法):左根右
    • 后序遍历(递归法,迭代法):左右根
  • 广度优先遍历

    • 层次遍历(迭代法)

2 二叉树递归遍历

2.1 前序遍历

使用递归进行前中后序遍历需要注意三点:

1 函数的定义、输入和输出 2 终止条件,一般是树为空时候返回 3 单层递归的逻辑。

在前序遍历中,先将根节点的val存储到结果中,在递归将左子树、右子树的前序遍历加到结果中。注意递归也有两种写法,第一种写法和回溯类似,tranverse函数无返回值,对类变量进行操作;第二种和动态规划类似需要有返回值,通过函数的定义来分解子问题,将子问题的返回值推导出最终答案。

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ''' 递归法1 '''
        self.res = []
        self.traverse(root)
        return self.res

    def traverse(self, root:TreeNode):
        if root is None:
            return

        self.res.append(root.val)
        self.traverse(root.left)
        self.traverse(root.right)
    
    def preorderTraversal2(self, root: Optional[TreeNode]) -> List[int]:
        ''' 递归法2:利用函数定义来分解问题 '''
        res = []
        if not root:
            return res

        res.append(root.val)
        res.extend(self.preorderTraversal2(root.left))
        res.extend(self.preorderTraversal2(root.right))
        return res

2.2 中序遍历

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ''' 递归法1 ''' 
        self.res = []
        self.traverse(root)
        return self.res

    def traverse(self, root:TreeNode):
        if root is None:
            return

        self.traverse(root.left)
        self.res.append(root.val)
        self.traverse(root.right)
    
    def inorderTraversal2(self, root: Optional[TreeNode]) -> List[int]:
        ''' 递归法2:利用函数定义来分解问题 '''
        res = []
        if not root:
            return res

        res.extend(self.inorderTraversal2(root.left))
        res.append(root.val)
        res.extend(self.inorderTraversal2(root.right))
        return res

2.3 后序遍历

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ''' 递归法1 ''' 
        self.res = []
        self.traverse(root)
        return self.res

    def traverse(self, root:TreeNode):
        if root is None:
            return

        self.traverse(root.left)
        self.res.append(root.val)
        self.traverse(root.right)
    
    def postorderTraversal2(self, root: Optional[TreeNode]) -> List[int]:
        ''' 递归法2:利用函数定义来分解问题 '''
        res = []
        if not root:
            return res

        res.extend(self.postorderTraversal2(root.left))
        res.extend(self.postorderTraversal2(root.right))
        res.append(root.val)
        
        
        return res

3 二叉树迭代遍历

3.1 前序遍历

使用递归的方法处理遍历时候,每一层递归调用会把函数的局部变量、参数值和返回地址都压入栈中,然后递归返回时候从栈顶弹出各项参数。因此可以使用栈结构来模拟递归的过程进行遍历:

image.png

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        if not root:
            return res
        stk = [root] # 栈记录变量的元素  根左右的压入顺序:先右再左,最先弹出左边元素
        while stk:
            top = stk.pop() # 弹出栈顶元素
            res.append(top.val)

            if top.right:
                stk.append(top.right)
            if top.left:
                stk.append(top.left)

        return res

3.2 中序遍历

中序比较特殊,根节点不像前序一样访问顺序和处理顺序一样。而是先访问到根节点,然后逐层往下到最左下的节点后才开始处理。在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        if not root:
            return res

        stk = [] # 注意先别加root!
        cur = root # 迭代到左子树的最左叶节点
        while cur or stk:
            if cur is not None:
                # 往左走
                stk.append(cur)
                cur = cur.left
            else:
                # 空了,回退到根节点,加入res后移动到right
                cur = stk.pop()
                res.append(cur.val)
                cur = cur.right

        return res

3.3 后序遍历

与前序遍历类似,需要得到左右根,先得到根右左,再反转结果。要得到根右左,需要在弹出一个节点时,在按照左孩子、右孩子的顺序加入到stk中。

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        if not root:
            return res
        stk = [root] # 栈记录变量的元素  根左右的压入顺序:先右再左,最先弹出左边元素
        while stk:
            top = stk.pop() # 弹出栈顶元素
            
            # 根右左
            res.append(top.val)
            if top.left:
                stk.append(top.left)
            if top.right:
                stk.append(top.right)
        
        # 左右根
        return res[::-1]

4 二叉树统一迭代遍历

TBC...

5 二叉树层序遍历

使用先进先出的队列存储树节点,每次迭代一层的节点,每个节点弹出时将非空的左右子节点依次加入队列。

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        res = []
        if not root:
            return res
        
        queue = [root]
        while queue:
            size = len(queue)
            level = [] # 存储当前元素

            for i in range(size):
                first = queue.pop(0)
                level.append(first.val)

                if first.left:
                    queue.append(first.left)
                
                if first.right:
                    queue.append(first.right)

            res.append(level) # 注意每一层需要单独放
        return res