二叉树

🧩树

节点类型

(1) 父节点 / 子节点
谁指向谁,指向的就是父节点,被指向的是子节点

(2) 兄弟节点
一个节点的所有子节点之间互相称为兄弟节点

(3) 根节点 / 叶节点 / 内部节点
没有父节点的就是根节点,在一棵非空树中,有且只有一个根节点。
没有子节点的就是叶节点
除去根节点和叶节点之外的其它节点就是内部节点

(4) 祖宗节点
祖宗结点是从根节点到该节点之前所经过的所有节点。

(5) 子孙节点
某个节点下直至叶子节点的所有节点,都是此节点的子孙节点

子树类型

以某个节点为根节点的树称为该节点的子树。

需要注意的是,各个子树一定是互不相交的。

树术语

(1) 层次

节点的层次是从根节点开始算起。

根节点是第一层,根节点的子节点层是第二层,依次往下类推。

(2) 深度

深度的话也是从根节点开始算起,依次往下是深度 1、2、...

最大深度为最大层数数。

(3) 高度

高度和深度正好相反,高度是从下往上。

叶子节点的高度是 1,叶子节点的父节点层高度是 2,依次往上类推。

(4) 森林

0个或多个不相交的树组成。对森林加上一个根,森林即成为树;删去根,树即成为森林。

🧩二叉树

二叉树定义

定义

二叉树 Binary Tree:是n(n>=0)个结点的有限集合,该集合或者空集(称为空二叉树),或者由一个根节点和两颗互不相交的,分别称为根节点的左子树和右子树的二叉树组成。
它是一种非线性数据结构,代表着祖先与后代之间的派生关系,体现着 “一分为二” 的分治逻辑。类似于链表,二叉树也是以结点为单位存储的,结点包含「值」和两个「指针」。

特点

(1)每个结点最多有两棵子树;
(2)左子树和右子树是有顺序的;
(3)即使树中某结点只有一颗子树,也要区分左右;

节点定义方式

class BTNode:
    def __init__(self,data):
        self.data = data
        self.lchild = None
        self.rchild = None

二叉树术语

「根结点 Root Node」:二叉树最顶层的结点,其没有父结点;

「叶结点 Leaf Node」:没有子结点的结点,其两个指针都指向 null ;

结点「度 Degree」:结点的子结点数量,二叉树中度的范围是 0, 1, 2 ;

结点「深度 Depth」 :根结点到该结点的层数;

结点「高度 Height」:最远叶结点到该结点的层数;

二叉树「高度」:二叉树中根结点到最远叶结点的层数;

binary_tree_terminology

二叉树的性质

在这里插入图片描述
  • 在二叉树的第i层最多有2^(i-1)个结点。

  • 深度为k的二叉树最多有2^k-1个结点。

  • 对任意一颗二叉树,如果其终端结点数为n0,度为2的结点数为n2,则n0= n2+1。

    一棵二叉树,除了终端结点(叶子结点),就是度为1或2的结点。假设n1度为1的结点数,则数T 的结点总数n=n0+n1+n2。我们再换个角度,看一下树T的连接线数,由于根结点只有分支出去,没有分支进入,所以连接线数为结点总数减去1。也就是n-1=n1+2n2,可推导出n0+n1+n2-1 = n1+2n2,继续推导可得n0 = n2+1。

  • 具有n个结点的完全二叉树的深度至少为[log2n]+1,其中[log2n]表示log2n的整数部分。

  • 如果根节点标号为1,则对任意结点i,它的左孩子为2i,右孩子为2i+1。

  • 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对
    于序号为i的结点有:

    1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
    2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
    3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

二叉树的基本形态

直观上看:每个节点最多只有两个叉的树叫二叉树。
既然是最多,那对于每个节点来说,可以一个叉,也可以没有叉,所以像下图这些都是二叉树。

img

二叉树最佳和最差结构

当二叉树的每层的结点都被填满时,达到完美二叉树。
当所有结点都偏向一边时,二叉树退化为链表。

binary_tree_corner_cases

在最佳和最差结构下,二叉树的结点数量和高度等性质达到最大(最小)值。

完美二叉树 链表
二叉树第 i 层的结点数量 2^(i−1) 1
高度为 h 的二叉树的结点总数 2^h−1 h
结点总数为 n 的二叉树的高度 log2⁡^n+1 n

二叉树的种类

除了上面讲的基本形态,还有三种特殊的二叉树:满二叉树、完全二叉树、二叉搜索树。

完美二叉树/满二叉树

定义

完美二叉树Perfect Binary Tree,其所有层的结点都被完全填满。

在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。

perfect_binary_tree

性质

  • 若树层数 =h ,则结点总数 2^h−1;
  • 在同等深度的二叉树中,满二叉树的节点个数最多

完全二叉树

定义

完全二叉树Complete Binary Tree,比较难理解,它的概念其实分前后两部分:
(1)除了最底层以外,其余的每一层节点数都是满的
(2)最底层的节点全集中在该层最左边的位置
最后一层如果是第 k 层,那么这一层最少有 1 个叶子节点,最多有 2^(k-1) 个节点,而这些节点都是从左到右依次排列。

complete_binary_tree

性质

  • 可以用数组表示
  • 若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点

完满二叉树

定义

完满二叉树Full Binary Tree,除了叶结点之外,其余所有结点都有两个子结点。

full_binary_tree

平衡二叉树

定义

平衡二叉树Balanced Binary Tree,其任意结点的左子树和右子树的高度之差的绝对值 ≤1

balanced_binary_tree

二叉搜索树

定义

二叉搜索树Binary Search Tree,又叫二叉排序树或二叉查找树。
前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树

binary_search_tree

性质

  • 若左子树不空,那左子树所有节点的值均 < 根节点的值。
  • 若右子树不空,那右子树所有节点的值均 > 根节点的值。
  • 左右子树也均为二叉搜索树。
  • 没有键值相等的节点(no duplicate nodes)

操作

查找节点

插入节点

删除节点

平衡二叉搜索树AVL

平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:

它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

https://www.hello-algo.com/chapter_tree/avl_tree/#_3

二叉树的存储方式

二叉树具有两种存储方式:顺序存储和链式存储。
链式存储方式就用指针, 顺序存储的方式就是用数组。

顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在散落在各个地址的节点串联一起。

顺序存储

img

不存在的节点,如缺失e节点,则节点设置为 ^ 表示。

用数组来存储二叉树如何遍历的呢?

如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。

但是用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。

所以大家要了解,用数组依然可以表示二叉树。

链式存储

对于每个节点来说,链式存储需要一个数据域 + 两个指针域。

img

二叉树的遍历

深度优先遍历

深度优先遍历:先走到尽头,遇到叶子节点再往回走。

如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围 “走” 一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。

binary_tree_dfs
位置 含义 此处访问结点时对应
橙色圆圈处 刚进入此结点,即将访问该结点的左子树 前序遍历 Pre-Order Traversal
蓝色圆圈处 已访问完左子树,即将访问右子树 中序遍历 In-Order Traversal
紫色圆圈处 已访问完左子树和右子树,即将返回 后序遍历 Post-Order Traversal

以 preOrder 表示前序遍历,以 inOrder 表示中序遍历,以 postOrder 表示后续遍历,则三者的递推公式为:

  • 前序:root -> preOrder(root.left) -> preOrder(root.right)
  • 中序:inOrder(root.left) -> root -> inOrder(root.right)
  • 后续:postOrder(root.left) -> postOrder(root->right) -> root

前序遍历

递归法

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

#写法一
class Solution:
    # 实现递归两部曲:
    # 1.找出重复子问题
    # 2.确定终止条件
    # 前序遍历:根-左-右
    # 前序遍历函数
    def preOrder(self, root: TreeNode, res):
        if root == None:    # 没有结点可以继续即停止
            return
        res.append(root.val) # 先取根节点
        self.preOrder(root.left, res)
        self.preOrder(root.right, res)

    def preorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        self.preOrder(root, res)
        return res

#写法二     
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        # 保存结果
        res = []
        def Traversal(root: TreeNode):
            if root == None:
                return
            res.append(root.val) # 前序
            Traversal(root.left)    # 左
            Traversal(root.right)   # 右

        Traversal(root)
        return res

由于每个节点被遍历一次,所以时间复杂度为 O(n)

额外维护了一个 res 列表,空间复杂度为 O(n)

迭代法

中序遍历

递归法

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

#写法一
class Solution:

    def inOrder(self, root:TreeNode, res):
        if root == None:
            return
        self.inOrder(root.left, res)
        res.append(root.val)
        self.inOrder(root.right, res)

    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        self.inOrder(root, res)
        return res
      
#写法二
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []

        def traversal(root: TreeNode):
            if root == None:
                return
            traversal(root.left)    # 左
            res.append(root.val) # 中序
            traversal(root.right)   # 右

        traversal(root)
        return res

中序遍历时间复杂度为 O(n)空间复杂度为 O(n)

迭代法

后续遍历

递归法

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

#写法一
class Solution:

    def postOrder(self, root: TreeNode, res):
        if root == None:
            return

        self.postOrder(root.left, res)
        self.postOrder(root.right, res)
        res.append(root.val)

    def postorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        self.postOrder(root, res)
        return res
      
#写法二
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        res = []

        def traversal(root: TreeNode):
            if root == None:
                return
            traversal(root.left)    # 左
            traversal(root.right)   # 右
            res.append(root.val) # 后序

        traversal(root)
        return res

后序遍历时间复杂度为 O(n)空间复杂度为 O(n)

迭代法

广度优先遍历

广度优先遍历:一层一层的去遍历。

层次遍历

binary_tree_bfs

递归法

例:

图片
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:

    def level(self, root: TreeNode, depth, res):
        if root == None:   # 最下面一层左右节点空了 即终止
            return []

        # 若当前行对应的列表不存在,加一个空列表
        if len(res) < depth:  # len(res)返回列表项数
            res.append([])

        # 将当前节点的值加入当前行的 res 中
        res[depth - 1].append(root.val)  # [depth - 1] 因为从0开始计

        # 递归处理左子树
        if root.left:
            self.level(root.left, depth + 1, res)  #处理下一层
        # 递归处理右子树
        if root.right:
            self.level(root.right, depth + 1, res)


    def levelOrder(self, root: TreeNode) -> List[List[int]]:

        res = []
        self.level(root, 1, res)

        return res

迭代法

posted @ 2022-12-11 20:57  happyfeliz  阅读(418)  评论(0)    收藏  举报