Task03

Task03 二叉树

3.1 树与二叉树的基础知识

1. 树简介

  • 树的定义:

由n≥0个节点与节点之间的关系组成的有限集合。当n=0时称为空树,当n>0时称为非空树。

树的节点(node):由一个数据元素和若干个指向其子树的树的分支组成。

节点的度:一个节点所含有的子树个数。

根节点(root):有且仅有一个,没有前驱节点的节点。

叶子节点(终端节点leaf node):度为0的节点。

分支节点(非终端节点):度不为0的节点。

子树(subtree): 每个节点包含它所有的后代组成的子树

树的度:树中节点的最大度数。

  • 节点间关系:

孩子节点(children):一个节点含有的子树的根节点称为该节点的子节点。

父亲节点(parent):如果一个节点含有子节点,则这个节点称为其子节点的父节点。

兄弟节点(siblings):具有相同父节点的节点互称为兄弟节点。

堂兄弟节点:父节点在同一层的节点互为堂兄弟。

节点的层次:从根节点开始定义,根为第1层,根的子节点为第2层,以此类推。

树的深度(高度):所有节点中最大的层数。

路径(path):树中两个节点之间所经过的节点序列。

路径长度:两个节点之间路径上经过的边数。

节点的祖先:从该节点到根节点所经过的所有节点,被称为该节点的祖先。

节点的子孙:节点的子树中所有节点被称为该节点的子孙。

  • 树的分类:

有序树:节点的各个⼦树从左⾄右有序, 不能互换位置。

无序树:节点的各个⼦树可互换位置。

2.二叉树简介

  • 二叉树的定义:

树中各个节点的度不大于 2 个的有序树,称为二叉树。通常树中的分支节点被称为 「左子树」 或 「右子树」。二叉树的分支具有左右次序,不能随意互换位置。(可以是空树)

  • 特殊的二叉树:

满二叉树:如果所有分支节点都存在左子树和右子树,并且所有叶子节点都在同一层上,则称该二叉树为满二叉树。

完全二叉树(Complete Binary Tree):如果叶子节点只能出现在最下面两层,并且最下层的叶子节点都依次排列在该层最左边的位置上,具有这种特点的二叉树称为完全二叉树。

二叉搜索树(Binary Search Tree):也叫做二叉查找树、有序二叉树或者排序二叉树。是指一棵空树或者具有下列性质的二叉树:

平衡二叉搜索树(Balanced Binary Tree):一种结构平衡的二叉搜索树。即叶节点高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉搜索树。平衡二叉树可以在O(logn)内完成插入、查找和删除操作。最早被发明的平衡二叉搜索树为 「AVL 树(Adelson-Velsky and Landis Tree))」。

  • 二叉树的存储结构:

顺序存储结构:使用一维数组来存储二叉树中的节点,节点存储位置则采用完全二叉树的节点层次编号,按照层次从上至下,每一层从左至右的顺序依次存放二叉树的数据元素。在进行顺序存储时,如果对应的二叉树节点不存在,则设置为「空节点」。

链式存储结构:每个链节点包含一个用于数据域 val,存储节点信息;还包含两个指针域 left 和 right,分别指向左右两个孩子节点,当左孩子或者右孩子不存在时,相应指针域值为空。

class TreeNode:
     def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

3.2二叉树的遍历

二叉树的遍历:指的是从根节点出发,按照某种次序依次访问二叉树中所有节点,使得每个节点被访问一次且仅被访问一次。

1.二叉树的前序遍历:

如果二叉树为空,则返回。
如果二叉树不为空,则:
1.访问根节点。
2.以前序遍历的方式遍历根节点的左子树。
3.以前序遍历的方式遍历根节点的右子树。

前序遍历递归实现:

import typing
from typing import List
from typing import Optional
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        
        #定义 preorder(root) 表示当前遍历到 root 节点的答案。
        def preorder(root):
            if not root:         #递归终止的条件为碰到空节点
                return
            res.append(root.val)  #先将 root 节点的值加入答案
            preorder(root.left)   #遍历 root 节点的左子树
            preorder(root.right)  #遍历 root 节点的右子树即可
        
        res = []
        preorder(root)
        return res

前序遍历显式栈实现(迭代):

二叉树的前序遍历递归实现的过程,实际上就是调用系统栈的过程。我们也可以使用一个显式栈 stack 来模拟递归的过程。

1.判断二叉树是否为空,为空则直接返回。
2.初始化维护一个栈,将根节点入栈。
3.当栈不为空时:
  弹出栈顶元素 node,并访问该元素。
  如果 node 的右子树不为空,则将 node 的右子树入栈。
  如果 node 的左子树不为空,则将 node 的左子树入栈。

由于栈是“先进后出”的顺序,所以入栈时先将右子树入栈,这样使得前序遍历结果为 “根->左->右”的顺序。

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:                        # 二叉树为空直接返回
            return []
            
        res = []
        stack = [root]

        while stack:                        # 栈不为空
            node = stack.pop()              # 弹出根节点
            res.append(node.val)            # 访问根节点
            if node.right:
                stack.append(node.right)    # 右子树入栈
            if node.left:
                stack.append(node.left)     # 左子树入栈

        return res

2.二叉树的中序遍历:

如果二叉树为空,则返回。
如果二叉树不为空,则:
1.以中序遍历的方式遍历根节点的左子树。
2.访问根节点。
3.以中序遍历的方式遍历根节点的右子树。

二叉树的中序遍历,一般在二叉搜索树(BST)中最为常用。

中序遍历递归实现:

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        
        #定义 inorder(root) 表示当前遍历到 root\textit{root}root 节点的答案
        def inorder(root):
            if not root:            #递归终止的条件为碰到空节点
                return
            inorder(root.left)      #遍历 root 节点的左子树
            res.append(root.val)    #将 root 节点的值加入答案
            inorder(root.right)     #遍历 root节点的右子树
        
        res = []
        inorder(root)
        return res

中序遍历显式栈实现:

1.判断二叉树是否为空,为空则直接返回。
2.初始化维护一个空栈。
3.当根节点或者栈不为空时:
  如果当前节点不为空,则循环遍历左子树,并不断将当前子树的根节点入栈。
  如果当前节点为空,说明当前节点无左子树,则弹出栈顶元素 node,并访问该元素,然后尝试访问该节点的右子树。

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:                # 二叉树为空直接返回
            return []
        
        res = []
        stack = []

        while root or stack:        # 根节点或栈不为空
            while root:             
                stack.append(root)  # 将当前树的根节点入栈
                root = root.left    # 找到最左侧节点
            
            node = stack.pop()      # 遍历到最左侧,当前节点无左子树时,将最左侧节点弹出
            res.append(node.val)    # 访问该节点
            root = node.right       # 尝试访问该节点的右子树
        return res

3.二叉树的后序遍历:

如果二叉树为空,则返回。
如果二叉树不为空,则:
1.以后序遍历的方式遍历根节点的左子树。
2.以后序遍历的方式遍历根节点的右子树。
3.访问根节点。

后序遍历递归实现:

class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        #定义 postorder(root) 表示当前遍历到 root 节点的答案。
        def postorder(root):
            if not root:          #递归终止的条件为碰到空节点
                return
            postorder(root.left)   #遍历 root 节点的左子树
            postorder(root.right)  #遍历 root 节点的右子树
            res.append(root.val)   #将 root 节点的值加入答案

        res = []
        postorder(root)
        return res

后序遍历显式栈实现:

判断二叉树是否为空,为空则直接返回。
初始化维护一个空栈,使用 prev 保存前一个访问的节点,用于确定当前节点的右子树是否访问完毕。
当根节点或者栈不为空时,从当前节点开始:
如果当前节点有左子树,则不断遍历左子树,并将当前根节点压入栈中。
如果当前节点无左子树,则弹出栈顶元素 node。
如果栈顶元素 node 无右子树(即 not node.right)或者右子树已经访问完毕(即 node.right == prev),则访问该元素,然后记录前一节点,并将当前节点标记为空节点。
如果栈顶元素有右子树,则将栈顶元素重新压入栈中,继续访问栈顶元素的右子树。

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        stack = []
        prev = None                 # 保存前一个访问的节点,用于确定当前节点的右子树是否访问完毕
        
        while root or stack:        # 根节点或栈不为空
            while root:
                stack.append(root)  # 将当前树的根节点入栈
                root = root.left    # 继续访问左子树,找到最左侧节点

            node = stack.pop()      # 遍历到最左侧,当前节点无左子树时,将最左侧节点弹出

            # 如果当前节点无右子树或者右子树访问完毕
            if not node.right or node.right == prev:
                res.append(node.val)# 访问该节点
                prev = node         # 记录前一节点
                root = None         # 将当前根节点标记为空
            else:
                stack.append(node)  # 右子树尚未访问完毕,将当前节点重新压回栈中
                root = node.right   # 继续访问右子树
                
        return res

4.二叉树的层序遍历:

二叉树的层序遍历是一个**广度优先搜索(BFS)**过程。在遍历的时候是按照第 1 层、第 2 层、…… 最后一层依次遍历的,而同一层节点则是按照从左至右的顺序依次访问的。

二叉树的层序遍历是通过队列来实现的。具体步骤如下:

1.判断二叉树是否为空,为空则直接返回。
2.令根节点入队。
3.当队列不为空时,求出当前队列长度。
4.依次从队列中取出这个元素,并对这个元素依次进行访问。然后将其左右孩子节点入队,然后继续遍历下一层节点。
5.当队列为空时,结束遍历。

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root:     #当根节点为空,则返回空列表 [] 
            return []
        queue = [root]    #根节点的队列
        order = []        #结果列表
        while queue:     #当队列 queue 为空时跳出
            level = []    #新建一个临时列表 level ,用于存储当前层打印结果
            size = len(queue)
            for _ in range(size):     #当前层打印循环: 循环次数为当前层节点数(即队列 queue 长度)
                curr = queue.pop(0)   #队首元素出队
                level.append(curr.val) #将节点的值加入答案
                if curr.left:        #若左子节点不为空,则将左子节点加入队列 queue
                    queue.append(curr.left)
                if curr.right:       #若右子节点不为空,则将右子节点加入队列 queue
                    queue.append(curr.right)
            if level:     #将当前层结果 level 添加入答案
                order.append(level)
        return order

3.3二叉树的还原

1.简介

二叉树的还原:指的是通过二叉树的遍历序列,还原出对应的二叉树。

如果已知一棵二叉树的前序序列和中序序列,可以唯一地确定这棵二叉树。
如果已知一棵二叉树的中序序列和后序序列,也可以唯一地确定这棵二叉树。
已知二叉树的「中序遍历序列」和「层序遍历序列」,也可以唯一地确定一棵二叉树。

如果已知二叉树的「前序遍历序列」和「后序遍历序列」,是不能唯一地确定一棵二叉树的。 这是因为没有中序遍历序列无法确定左右部分,也就无法进行子序列的分割。

2.从前序与中序遍历序列构造二叉树

步骤如下:

1.从前序遍历顺序中得到当前根节点的位置在 postorder[0]。
2.通过在中序遍历中查找上一步根节点对应的位置 inorder[k],从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。
3.从上一步得到的左右子树个数将前序遍历结果中的左右子树分开。
4.构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。


class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        def createTree(preorder, inorder, n):
            if n == 0:
                return None
            k = 0
            while preorder[0] != inorder[k]:
                k += 1
            node = TreeNode(inorder[k])
            node.left = createTree(preorder[1: k + 1], inorder[0: k], k)
            node.right = createTree(preorder[k + 1:], inorder[k + 1:], n - k - 1)
            return node
        return createTree(preorder, inorder, len(inorder))

3. 从中序与后序遍历序列构造二叉树

步骤如下:

1.从后序遍历顺序中当前根节点的位置在 postorder[n-1]。
2.通过在中序遍历中查找上一步根节点对应的位置 inorder[k],从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。
3.从上一步得到的左右子树个数将后序遍历结果中的左右子树分开。
4.构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。

class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        def createTree(inorder, postorder, n):
            if n == 0:
                return None
            k = 0
            while postorder[n - 1] != inorder[k]:
                k += 1
            node = TreeNode(inorder[k])
            node.right = createTree(inorder[k + 1: n], postorder[k: n - 1], n - k - 1)
            node.left = createTree(inorder[0: k], postorder[0: k], k)
            return node
        return createTree(inorder, postorder, len(postorder))

4. 从前序与后序遍历序列构造二叉树(不要求构造的二叉树是唯一的)

步骤如下:

1.从前序遍历序列中可知当前根节点的位置在 preorder[0]。
2.前序遍历序列的第 2 个值为左子树的根节点,即 preorder[1]。通过在后序遍历中查找上一步根节点对应的位置 postorder[k](该节点右侧为右子树序列),从而将二叉树的左右子树分隔开,并得到左右子树节点的个数。
3.从上一步得到的左右子树个数将后序遍历结果中的左右子树分开。
4.构建当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。

class Solution:
    def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> TreeNode:
        def createTree(preorder, postorder, n):
            if n == 0:
                return None
            node = TreeNode(preorder[0])
            if n == 1:
                return node
            k = 0
            while postorder[k] != preorder[1]:
                k += 1
            node.left = createTree(preorder[1: k + 2], postorder[: k + 1], k + 1)
            node.right = createTree(preorder[k + 2: ], postorder[k + 1: -1], n - k - 2)
            return node
        return createTree(preorder, postorder, len(preorder))

二叉树的递归方法使用可以分两类思路:

第一类是直接遍历一遍二叉树,使用前序遍历或后序遍历。
第二类是分解问题,变为通过子问题求解,常常实用后序遍历。
遇到问题是否可以通过遍历一遍二叉树得到答案(前序遍历或后序遍历)?

如果不能的话,是否可以通过分解问题,用子问题(子树)的答案推导出原问题的答案(后序遍历)。

3.5练习(day8)

1. 二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的前序遍历。

思路:可使用递归或迭代,详见3.2.1

2. 二叉树的中序遍历

给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。

思路:可使用递归或迭代,详见3.2.2

3. 二叉树的后序遍历

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。

思路:可使用递归或迭代,详见3.2.3

3.6练习(day9)

1. 二叉树的层序遍历

给你二叉树的根节点 root ,返回其节点值的层序遍历 。 (即逐层地,从左到右访问所有节点)。

思路:广度优先搜索(BFS),详见3.2.4

2. 二叉树的最大深度

给定一个二叉树 root ,返回其最大深度。

二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

思路:深度优先搜索(DFS),递归遍历二叉树左子树和右子树,取最大深度,加1(根节点)

class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root:    #递归终止的条件为碰到空节点
            return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1 # 递归,左右子树的最大深度

3. 路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。

叶子节点 是指没有子节点的节点。

思路:深度优先搜索,向下寻找叶子节点,当找到叶子节点时sum==0,说明该路径符合要求

class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return False
        if not root.left and not root.right:
            return targetSum == root.val
        return self.hasPathSum(root.left, targetSum - root.val) or self.hasPathSum(root.right, targetSum - root.val)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值