基本数据结构:“树”的简单介绍

是一种重要的非线性数据结构,它模拟了树这种自然结构,由结点(Node)和边(Edge)组成,常用于表示分层关系(如文件系统、组织结构等)。以下是树的一些基本概念和特性:

1.树的基本概念

1.1 结点(Node)

  • 树由多个结点组成。
  • 每个结点包含一个值或数据。
  • 结点可以有零个或多个子结点。

1.2 根结点(Root Node)

  • 树的最顶层的结点称为根结点。
  • 每棵树只有一个唯一的根结点。

1.3 叶结点(Leaf Node)

  • 也称为终端结点,没有子结点的结点。
  • 叶结点是树的最底层结点。

1.4 内部结点(Internal Node)

  • 具有至少一个子结点的结点。

1.5 父结点(Parent Node)

  • 具有下一级子结点的结点称为父结点。
  • 除了根结点之外,每个结点都有一个父结点。

1.6 子结点(Child Node)

  • 父结点的直接下一级结点称为子结点。

1.7 兄弟结点(Sibling Node)

  • 具有相同父结点的结点称为兄弟结点。

1.8 路径(Path)

  • 从一个结点到另一个结点的顺序访问称为路径。

1.9 深度(Depth)

  • 从根结点到某个结点的路径长度称为该结点的深度。

1.10 高度(Height)

- 从某个结点到叶结点的最长路径的长度称为该结点的高度。

2.树的类型

2.1 二叉树(Binary Tree)

  • 每个结点最多有两个子结点(左子结点和右子结点)。

2.2 完全二叉树(Complete Binary Tree)

2.2.1完全二叉树的定义

完全二叉树(Complete Binary Tree)是一种特殊的二叉树,满足以下两个条件:

所有层(除最后一层外)都是满的:即每层包含的结点数都是最大可能的。
最后一层的结点尽量靠左排列:即最后一层上的结点都是从左到右填充的,没有空隙。

2.2.2完全二叉树的性质

完全二叉树具有一些独特的性质,使其在实践中非常有用:

  1. 结点数量和层次关系

    • 对于一棵高度为 (h) 的完全二叉树,结点总数 (n) 满足 (2^{h-1} \leq n < 2^h )
    • 如果完全二叉树有 (n) 个结点,那么它的高度 (h) 为 (h = \lfloor \log_2{n} \rfloor + 1)
  2. 结点的编号

    • 完全二叉树的结点可以按层次从上到下、从左到右依次编号。
    • 编号为 (i) 的结点的父结点编号为 (i/2),左子结点编号为 (2i),右子结点编号为 (2i+1)。
  3. 满二叉树的关系

    • 完全二叉树是一种特定类型的不完全的满二叉树,即所有结点要么有两个子结点,要么没有子结点。
  4. 树的层数

    • 所有结点按层从上到下编号后,编号为 (i) 的结点所在层数为 (\lfloor \log_2{i} \rfloor + 1)
2.2.3完全二叉树的实现

由于完全二叉树的这些性质,可以非常方便地使用数组来表示它。这种表示法使得父、子结点间的关系易于计算。

2.2.4数组表示法

假设数组从下标 1 开始存储完全二叉树的结点:

  • 若根结点在数组的第 1 位(arr[1]
    • 下标为 (i) 的结点的左子结点下标为 (2i)。
    • 下标为 (i) 的右子结点下标为 (2i+1)。
    • 下标为 (i) 的结点的父结点下标为 (i/2)。
2.2.5完全二叉树的应用
  1. 堆(Heap)

    • 堆是一种完全二叉树,可以是最大堆(根结点是最大值)或最小堆(根结点是最小值)。
    • 应用于优先队列等数据结构。
  2. 树状数组(Fenwick Tree)

    • 用作高效动态处理前缀和问题。
  3. 缓存机制

    • 完全二叉树的性质使其在某些存储结构(如B树、B+树)中得以应用,优化存储访问效率。
2.2.6完全二叉树的例子

考虑一个完全二叉树,有如下性质:

          1
       /    \
      2      3
     / \    / 
    4   5  6 
  • 这种结构中,除了第3层,其他层都是满的,最后一层的结点(4, 5, 6)从左到右依次排列。
2.2.7总结

完全二叉树因其紧凑的结构和优良的性质,在实际应用中广泛存在。它的层次性表示使得在某些情况下,它可以非常高效地被存储和操作。只需简单的数组操作,即可轻松完成许多复杂的数据结构操作,从而提升算法的性能。

2.3 满二叉树(Full Binary Tree)

  • 每个结点要么没有子结点,要么有两个子结点。
2.3.1 满二叉树定义

**满二叉树(Full Binary Tree 或 Perfect Binary Tree)**是一种特殊的二叉树,它有以下特点:

  1. 每一个结点要么是叶子结点,要么有两个子结点:即没有只有一个子结点的结点。
  2. 所有叶子结点都在同一层:这一点区分了满二叉树与其他类型的二叉树,如完全二叉树。
2.3.2 满二叉树的性质

满二叉树具有一些独特且重要的性质,使其在理论和实际应用中广泛使用。

  1. 结点数量与高度关系

    • 若高度为 ( h ) 的满二叉树,结点总数 ( n ) 满足 ( n = 2^h - 1 )。
    • 若结点总数为 ( n ),则高度 ( h ) 满足 ( h = \log_2(n + 1) )。
  2. 叶子结点数量

    • 满二叉树中,叶子结点数量为 ( 2^{h-1} ),其中 ( h ) 是树的高度。
  3. 内部结点数量

    • 满二叉树中,内部结点的数量为 ( 2^{h-1} - 1 )。
  4. 平衡性

    • 满二叉树是完全平衡的,即所有叶子结点到根结点的路径长度相同。
  5. 最优性

    • 对于给定高度 ( h ) 的树结构来说,满二叉树拥有最多的结点数,是结点高度分布最优的二叉树。
2.3.3 满二叉树的例子

考虑一个高度为 3 的满二叉树,看起来如下:

          1
       /     \
      2       3
     / \     / \
    4   5   6   7

在这个例子中:

  • 树的高度 ( h = 3 )。
  • 结点总数 ( n = 2^3 - 1 = 7 )。
  • 叶子结点数量 ( = 2^{3-1} = 4 ) 个(4, 5, 6, 7)。
2.3.4 满二叉树在数组中的表示

满二叉树可以使用数组方便地表示,有如下规则:

  • 根结点在数组的第 1 位(arr[1]
  • 下标为 ( i ) 的结点的左子结点下标为 ( 2i )。
  • 下标为 ( i ) 的结点的右子结点下标为 ( 2i + 1 )。
  • 下标为 ( i ) 的结点的父结点下标为 ( i/2 )。
2.3.5 应用场景

满二叉树在计算机科学中有很多应用场景,特别是在需要平衡和对称性的场景中:

  1. 计算机网络中层次结构设计:例如IP地址的分配等。
  2. 数据库和文件系统的设计:特别是在设计完全平衡树类型的索引结构时。
  3. 递归算法分析:例如在深度优先搜索中的递归树。
  4. 数据压缩:一些数据压缩算法(如Huffman编码)中,会用到树状结构。
2.3.6 结论

满二叉树因其高度平衡和结点分布均匀的特性,在很多理论研究和实际应用中都是一个重要的概念。它的数学性质使得我们可以轻松地计算和推导出各种相关参数,这对于分析和设计高效的算法和数据结构至关重要。

2.4 平衡二叉树(Balanced Binary Tree)

  • 任何结点的左右子树的高度差不超过1。
  • 如:AVL树、红黑树。
2.4.1 平衡二叉树定义

平衡二叉树(Balanced Binary Tree) 又称 AVL树(得名于其发明者 Adelson-Velsky 和 Landis),是一种特殊的二叉搜索树(BST)。它具有以下特点:

  1. 二叉搜索树的性质:对于任何一个结点,其左子树所有结点的值均小于该结点,右子树所有结点的值均大于该结点。
  2. 高度平衡:任何结点的左右子树高度差不超过 1。
2.4.2 平衡因子

平衡二叉树通过维护平衡因子来实现其高度平衡:

  • 平衡因子:某个结点的平衡因子等于其左子树高度减去右子树高度。AVL 树中的任意结点的平衡因子的绝对值不超过 1。
2.4.3 AVL树的旋转操作

当插入或删除结点导致某个结点的平衡因子不再满足条件(绝对值超过 1)时,需要通过旋转操作来重新平衡树。常见的旋转操作有:

  1. 右旋(LL旋转)

    • 当某个结点的左子树的左子树插入导致不平衡。
    • 旋转示意:
           y                  x
          / \                / \
         x   C     -->      A   y
        / \                    / \
       A   B                  B   C
      
  2. 左旋(RR旋转)

    • 当某个结点的右子树的右子树插入导致不平衡。
    • 旋转示意:
           x                  y
          / \                / \
         A   y     -->      x   C
            / \            / \
           B   C          A   B
      
  3. 左右旋(LR旋转)

    • 当某个结点的左子树的右子树插入导致不平衡。
    • 先对左子结点进行左旋,再对根结点进行右旋。
    • 旋转示意:
           z                 z                 y
          / \               / \               / \
         x   D     -->     y   D     -->     x   z
        / \               / \               / \ / \
       A   y             x   C             A  B C  D
          / \           / \
         B   C         A   B
      
  4. 右左旋(RL旋转)

    • 当某个结点的右子树的左子树插入导致不平衡。
    • 先对右子结点进行右旋,再对根结点进行左旋。
    • 旋转示意:
           x                 x                 y
          / \               / \               / \
         A   z     -->     A   y     -->     x   z
            / \               / \           / \ / \
           y   D             B   z         A  B C  D
          / \                   / \
         B   C                 C   D
      
2.4.4 AVL树的操作

基本操作包括插入删除查找

  1. 插入

    • 插入结点后,可能导致某些结点失去平衡。需要回溯检查并通过旋转操作来恢复平衡。
  2. 删除

    • 删除结点后,也可能导致某些结点失去平衡。通过旋转操作恢复平衡。
  3. 查找

    • 查找操作与普通的二叉搜索树相同,时间复杂度为 (O(log n))。
2.4.5 AVL树的时间复杂度

由于 AVL 树保持了高度平衡,树的高度 (h) 始终在 (O(log n)) 级别。因此,插入、删除和查找操作的时间复杂度均为 (O(log n))。

2.4.6 AVL树的结构例子

考虑以下一个 AVL 树例子:

        30
       /  \
     20    40
    /  \     \
   10   25    50

在这个例子中:

  • 每个结点的左右子树高度差不超过 1,满足 AVL 树的条件。
2.4.6 AVL树的优缺点

优点

  • 查找、插入和删除操作都有较好的时间复杂度 (O(log n))。
  • 适用于需要频繁插入和删除操作的动态数据集。

缺点

  • 旋转操作相对复杂,可能引入一些开销。
  • 维护树的平衡需要一定的额外计算资源。
2.4.7 结论

AVL 树以其平衡二叉搜索树的特性,在需要高效进行查找和动态插入、删除操作的场景中尤为重要。通过旋转操作保持平衡,使得树的高度始终控制在对数级别,从而保证高效的操作时间。

2.5 二叉搜索树(Binary Search Tree, BST)

  • 每个结点的左子树上的所有结点值都小于该结点的值,而右子树上的所有结点值都大于该结点的值。
2.5.1 二叉搜索树的定义

二叉搜索树是一种二叉树,它具有以下特性:

  1. 每个结点有一个唯一的键值或者数据。
  2. 左子树所有结点的键值小于其根结点的键值。
  3. 右子树所有结点的键值大于其根结点的键值。
  4. 每个子树也是一个二叉搜索树。
2.5.2 二叉搜索树的性质
  1. 中序遍历:对二叉搜索树进行中序遍历(左-根-右)可以得到一个递增的有序序列。
  2. 查找、插入与删除操作的时间复杂度:在平均情况下,这些操作的时间复杂度均为 (O(log n)),但在最坏情况下(树退化成链表),时间复杂度为 (O(n))。
  3. 最低与最高结点:最小值结点是一直沿左子树遍历得到的结点,最大值结点是一直沿右子树遍历得到的结点。
2.5.3 二叉搜索树的基本操作

1. 查找(Search)
在树中查找一个值,按照以下步骤:

  • 从根结点开始比较。
  • 若值等于根结点,查找成功。
  • 若值小于根结点,递归查找左子树。
  • 若值大于根结点,递归查找右子树。
def search(root, key):
    if root is None or root.val == key:
        return root
    if key < root.val:
        return search(root.left, key)
    else:
        return search(root.right, key)

2. 插入(Insert)
在树中插入一个新值,需要保持树的性质:

  • 若树为空,把新结点作为根结点。
  • 若树非空,比较新值与当前结点值:
    • 若小于当前结点,递归插入到左子树。
    • 若大于当前结点,递归插入到右子树。
def insert(root, key):
    if root is None:
        return TreeNode(key)
    if key < root.val:
        root.left = insert(root.left, key)
    else:
        root.right = insert(root.right, key)
    return root

3. 删除(Delete)
删除结点时需要维护树的性质,有三种情况:

  • 结点无子结点:直接删除。
  • 结点有一个子结点:用子结点替代被删除结点。
  • 结点有两个子结点:找到右子树的最小值结点或左子树的最大值结点替换被删除结点,然后递归删除最小值/最大值结点。
def delete(root, key):
    if root is None:
        return root
    
    if key < root.val:
        root.left = delete(root.left, key)
    elif key > root.val:
        root.right = delete(root.right, key)
    else:
        if root.left is None:
            return root.right
        elif root.right is None:
            return root.left
        temp = minValueNode(root.right)  # 找右子树最小值结点
        root.val = temp.val
        root.right = delete(root.right, temp.val)
    return root

def minValueNode(node):
    current = node
    while current.left is not None:
        current = current.left
    return current

4. 前序遍历(Pre-order Traversal)
遍历顺序为根结点 -> 左子树 -> 右子树。

5. 中序遍历(In-order Traversal)
遍历顺序为左子树 -> 根结点 -> 右子树。对BST的中序遍历结果是一个排序的序列。

6. 后序遍历(Post-order Traversal)
遍历顺序为左子树 -> 右子树 -> 根结点。

2.5.4 二叉搜索树的应用
  1. 查找表:如字典、集合等,基于BST可以高效地实现查找、插入和删除操作。
  2. 排序:通过中序遍历可以得到排序后的数据。
  3. 动态数据集合:特别是在需要动态维护数据集的场景中,BST非常有效。
2.5.5 二叉搜索树的例子

考虑一个简单的二叉搜索树:

       15
     /    \
   10      20
  /  \    /  \
 8   12  17  25

在这个例子中:

  • 查找 12,通过根结点 15 向左,再向右,找到了结点 12。
  • 插入 16,首先找到 17 的位置,然后插入 16 作为 17 的左子结点。
  • 删除 20,选择 25 替代 20 的位置,然后从右子树中删除 25,再连接。
2.5.6 二叉搜索树的优缺点

优点

  • 实现简单,直观易懂。
  • 可以高效地实现查找、插入和删除操作,特别是在平均情况下。

缺点

  • 在最坏情况下,树可能退化为链表,导致操作时间复杂度为 (O(n))。
  • 不自带平衡机制,需要引入平衡策略(如 AVL 树或红黑树)来优化。
2.5.7 总结

二叉搜索树作为一种基本且重要的数据结构,在很多算法和数据存储中扮演重要角色。理解并掌握它的基本操作和性质,有助于设计高效的算法,并打下坚实的数据结构基础。

2.6 N叉树(B-Tree、B+树)

  • 每个结点可以有最多N个子结点,常用于数据库和文件系统。
2.6.1 N叉树的定义

N叉树(N-ary Tree)是一种广义的树形数据结构,其中每个结点最多可以有 N 个子结点。N叉树是二叉树的推广,在二叉树中每个结点至多有两个子结点,而在N叉树中,每个结点可以有更多的子结点。

2.6.2 N叉树的性质
  1. 结点和子结点:每个结点最多可以有 N 个子结点。
  2. 树的高度:由根结点到叶子结点所经过的边的最大数量。
  3. 叶子结点:没有子结点的结点。
  4. 树的度:树中结点拥有的最大子结点数。
2.6.3 N叉树的表示
2.6.3.1 数组表示法

在数组表示法中,N叉树的每一个结点都包含自己的数据和一个子结点数组。这个数组包含指向所有子结点的指针。

class Node:
    def __init__(self, val=None):
        self.val = val
        self.children = []
2.6.3.2 链表表示法

在链表表示法中,N叉树的每个结点不仅存储数据,还包含一个指向其子结点的列表(可以是链表、数组等)。

2.6.4 N叉树的基本操作

1. 插入(Insert)
插入操作根据特定的需求和策略,可以将新结点插入到不同的位置。例如,可以选择插入到某个结点的子结点列表中。

def insert(parent, child):
    if parent is not None:
        parent.children.append(child)

2. 删除(Delete)
删除操作通常也根据特定要求进行,可以删除某个结点及其子树,或者仅删除某个结点而保留其子结点。

def delete(parent, child):
    if parent is not None:
        parent.children.remove(child)

3. 遍历(Traversal):N叉树的遍历可以类比二叉树的遍历方法,分别有前序、中序、后序和层序遍历。

  • 前序遍历(Pre-order Traversal):根 -> 子结点。
def preorder(node):
    if not node:
        return
    print(node.val, end=' ')
    for child in node.children:
        preorder(child)
  • 后序遍历(Post-order Traversal):子结点 -> 根。
def postorder(node):
    if not node:
        return
    for child in node.children:
        postorder(child)
    print(node.val, end=' ')
  • 层次遍历(Level-order Traversal):一层一层地遍历树的结点。
from collections import deque

def level_order(root):
    if not root:
        return
    queue = deque([root])
    while queue:
        node = queue.popleft()
        print(node.val, end=' ')
        for child in node.children:
            queue.append(child)
2.6.5 N叉树的应用

N叉树由于其灵活的结点数量,广泛应用于多种数据结构和现实问题中:

  1. 文件系统:N叉树可以用来表示目录和文件的层次结构,每个目录包含多个子目录和文件。
  2. 分类体系:如产品分类,种属分类等,每个分类下可以有多个子分类。
  3. 游戏开发:用于表示场景中的层次结构,如场景图和对象之间的层次关系。
  4. 组织架构:公司或组织的层次结构,每个部门可以有多个子部门。
2.6.6 N叉树的例子

考虑一个3叉树的结构示例:

        1
      / | \
     2  3  4
    /|\    |
   5 6 7   8

在这个3叉树中:

  • 根结点是 1。
  • 结点 1 有 3 个子结点(2、3、4)。
  • 结点 2 有 3 个子结点(5、6、7)。
  • 结点 4 有 1 个子结点(8)。
2.6.7 N叉树的优缺点

优点

  • 可以高效管理具有层次性和多子结点的数据结构。
  • 适用于需要灵活控制结点子结点数量的场景。

缺点

  • 实现和操作相对复杂,特别是对于一些较高阶的操作。
  • 内存消耗较高,因为每个结点存储多个子结点的信息。
2.6.8 总结

N叉树是一种广义的树形数据结构,能够有效地表示和操作具有多子结点的复杂层次关系。其灵活性和扩展性使其在许多应用场景中非常实用。理解其基本结构和操作方法,可以帮助设计更复杂和更高效的数据管理系统。

3.树的遍历

树的遍历是一种访问结点的方式,通常有几种基本的遍历方式:

3.1 先序遍历(Pre-order Traversal)

  • 访问根结点→左子树→右子树。

3.2 中序遍历(In-order Traversal)

  • 访问左子树→根结点→右子树。
  • 用于二叉搜索树时,中序遍历可以生成排序的序列。

3.3 后序遍历(Post-order Traversal)

  • 访问左子树→右子树→根结点。

3.4 层次遍历(Level-order Traversal)

  • 逐层访问结点(使用队列实现)。

4.树的应用

4.1 文件系统

  • 操作系统的文件系统中目录结构通常用树表示。

4.2 数据库索引

  • B树和B+树广泛用于数据库和文件系统的索引。

4.3 表达式解析

  • 树用于编译器中表达式解析。

4.4 网络路由

  • 路由协议经常使用树来存储路由信息。

4.5 人工智能

  • 游戏中的决策树和搜索树。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值