零基础数据结构与算法——第三章:高级数据结构-树(下)

发布于:2025-07-01 ⋅ 阅读:(15) ⋅ 点赞:(0)

3.1 树(上)

3.1 树(下)

3.1.5 平衡二叉搜索树

平衡二叉搜索树是一种特殊的二叉搜索树,它通过某种机制保证树的高度平衡,从而保证操作的时间复杂度为O(log n)。当一棵普通的二叉搜索树变得不平衡时(例如,一侧的子树明显比另一侧深),搜索效率会大大降低。

为什么需要平衡二叉搜索树?

想象一下,如果我们按照以下顺序向一棵空的二叉搜索树中插入数据:1, 2, 3, 4, 5

我们会得到这样一棵树:

1
 \
  2
   \
    3
     \
      4
       \
        5

这棵树已经退化成了一个链表,此时查找、插入和删除操作的时间复杂度都变为O(n),失去了二叉搜索树的优势。

平衡二叉搜索树通过自动调整树的结构,确保树的高度保持在O(log n),从而保证操作的效率。

常见的平衡二叉搜索树
1. AVL树

AVL树是最早被发明的自平衡二叉搜索树之一,由G.M. Adelson-Velsky和E.M. Landis在1962年提出。

特点

  • 每个节点的左右子树高度差不超过1
  • 每次插入或删除操作后,会通过旋转操作来恢复平衡
  • 查找、插入和删除操作的时间复杂度都是O(log n)

平衡因子

  • 定义为右子树高度减去左子树高度
  • 对于AVL树,每个节点的平衡因子只能是-1、0或1

旋转操作

  • 左旋(Left Rotation)
  • 右旋(Right Rotation)
  • 左-右旋(Left-Right Rotation)
  • 右-左旋(Right-Left Rotation)

生活例子

想象一个杂技演员在平衡木上保持平衡。当他感觉一侧重量过大时,会通过移动身体来重新分配重量,使两侧保持平衡。AVL树也是如此,通过旋转操作来保持左右子树的高度差不超过1。

AVL树的Java实现示例

class AVLNode {
    int data;
    AVLNode left, right;
    int height;  // 节点的高度

    public AVLNode(int data) {
        this.data = data;
        this.height = 1;  // 新节点的高度为1
    }
}

class AVLTree {
    AVLNode root;

    // 获取节点的高度
    int height(AVLNode node) {
        if (node == null)
            return 0;
        return node.height;
    }

    // 获取节点的平衡因子
    int getBalance(AVLNode node) {
        if (node == null)
            return 0;
        return height(node.right) - height(node.left);
    }

    // 右旋转
    AVLNode rightRotate(AVLNode y) {
        AVLNode x = y.left;
        AVLNode T2 = x.right;

        // 执行旋转
        x.right = y;
        y.left = T2;

        // 更新高度
        y.height = Math.max(height(y.left), height(y.right)) + 1;
        x.height = Math.max(height(x.left), height(x.right)) + 1;

        // 返回新的根节点
        return x;
    }

    // 左旋转
    AVLNode leftRotate(AVLNode x) {
        AVLNode y = x.right;
        AVLNode T2 = y.left;

        // 执行旋转
        y.left = x;
        x.right = T2;

        // 更新高度
        x.height = Math.max(height(x.left), height(x.right)) + 1;
        y.height = Math.max(height(y.left), height(y.right)) + 1;

        // 返回新的根节点
        return y;
    }

    // 插入节点
    public void insert(int data) {
        root = insertNode(root, data);
    }

    private AVLNode insertNode(AVLNode node, int data) {
        // 1. 执行标准BST插入
        if (node == null)
            return new AVLNode(data);

        if (data < node.data)
            node.left = insertNode(node.left, data);
        else if (data > node.data)
            node.right = insertNode(node.right, data);
        else // 重复值不插入
            return node;

        // 2. 更新当前节点的高度
        node.height = Math.max(height(node.left), height(node.right)) + 1;

        // 3. 获取平衡因子
        int balance = getBalance(node);

        // 4. 如果节点不平衡,则有四种情况

        // 左左情况 - 右旋
        if (balance < -1 && data < node.left.data)
            return rightRotate(node);

        // 右右情况 - 左旋
        if (balance > 1 && data > node.right.data)
            return leftRotate(node);

        // 左右情况 - 先左旋后右旋
        if (balance < -1 && data > node.left.data) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }

        // 右左情况 - 先右旋后左旋
        if (balance > 1 && data < node.right.data) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }

        // 返回未更改的节点指针
        return node;
    }

    // 中序遍历
    public void inOrder() {
        inOrderTraversal(root);
        System.out.println();
    }

    private void inOrderTraversal(AVLNode node) {
        if (node != null) {
            inOrderTraversal(node.left);
            System.out.print(node.data + " ");
            inOrderTraversal(node.right);
        }
    }
}
2. 红黑树

红黑树是另一种常用的自平衡二叉搜索树,它在许多编程语言的标准库中被广泛使用(如Java的TreeMap和TreeSet,C++的map和set)。

特点

  • 每个节点要么是红色,要么是黑色
  • 根节点是黑色
  • 所有叶子节点(NIL节点)是黑色
  • 如果一个节点是红色,则它的两个子节点都是黑色
  • 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点

优势

  • 插入和删除操作的平均时间复杂度为O(log n)
  • 相比AVL树,红黑树在插入和删除操作时需要的旋转次数更少
  • 虽然红黑树的平衡条件不如AVL树严格,但对于大多数应用来说已经足够好

生活例子

想象一个交通信号灯系统,红灯表示停止,黑灯表示通行。红黑树中的红色和黑色节点就像这样的信号灯,通过一系列规则来控制树的结构,确保树的高度保持在O(log n)。

红黑树的应用

  • Java集合框架中的TreeMap和TreeSet
  • Linux内核中的完全公平调度器
  • C++ STL中的map和set
  • 许多数据库的实现
3. 其他平衡二叉搜索树

伸展树(Splay Tree)

  • 一种自调整的二叉搜索树
  • 每次访问一个节点后,通过旋转操作将该节点移动到根节点位置
  • 适合于频繁访问相同元素的场景

B树和B+树

  • 多路搜索树,每个节点可以有多个子节点
  • 广泛应用于数据库和文件系统
  • 特别适合磁盘等外部存储设备的数据结构
平衡二叉搜索树的应用场景
  1. 数据库索引:B树和B+树是数据库索引的标准实现
  2. 内存管理:用于跟踪内存块的分配和释放
  3. 文件系统:用于组织和快速访问文件
  4. 地图和导航系统:用于存储地理位置数据
  5. 网络路由:用于路由表的实现
平衡二叉搜索树的选择
  • AVL树:当查询操作远多于插入和删除操作时,选择AVL树
  • 红黑树:当插入和删除操作频繁时,选择红黑树
  • B树/B+树:当数据量非常大,无法全部加载到内存时,选择B树或B+树

3.1.6 树的应用场景

树是一种非常实用的数据结构,在计算机科学和日常生活中有广泛的应用。下面我们通过生活中的例子来理解树的各种应用场景。

1. 文件系统的目录结构

生活例子:想象你的电脑文件管理器或手机相册。文件夹可以包含子文件夹,形成一个层次结构。

/根目录
├── 文档
│   ├── 工作
│   │   ├── 报告.docx
│   │   └── 演示.pptx
│   └── 个人
│       └── 简历.pdf
├── 图片
│   ├── 假期
│   └── 家人
└── 音乐
    ├── 流行
    └── 古典

这种结构本质上是一棵树,其中:

  • 根目录是树的根节点
  • 每个文件夹是一个内部节点
  • 每个文件是一个叶子节点

应用:所有现代操作系统的文件系统都使用树结构来组织文件和目录。

2. 组织结构图

生活例子:一个公司的组织架构,从CEO开始,下面是各个部门的副总裁,再下面是经理,然后是普通员工。

       CEO
       / | \
      /  |  \
     /   |   \
   VP1  VP2  VP3
  / |    |    | \
 经理 经理 经理 经理 经理
 / |  |   |   |   | \
员工 员工 员工 员工 员工 员工

应用:人力资源管理系统、公司通讯录、权限管理系统。

3. 数据库索引

生活例子:图书馆的图书分类系统。图书按照主题、作者、出版日期等进行分类,使读者能够快速找到所需的书籍。

技术实现:数据库使用B树或B+树来实现索引,加速数据查询。

应用:几乎所有的关系型数据库(如MySQL、Oracle、SQL Server)都使用树结构来实现索引。

4. 决策树

生活例子:医生诊断疾病的过程。医生会问一系列问题,根据患者的回答来缩小可能的疾病范围,最终做出诊断。

                 发烧吗?
                /        \
              是          否
             /            \
        咳嗽吗?         头痛吗?
        /    \          /    \
      是     否        是     否
     /        \      /        \
 可能是感冒  可能是其他  可能是偏头痛  可能是其他

应用

  • 医疗诊断系统
  • 客户服务中的问题分类
  • 机器学习中的分类算法
  • 专家系统
5. 游戏中的AI决策

生活例子:下棋时,棋手会思考"如果我走这步,对方可能会怎么走,然后我应该怎么应对…",这种思考过程可以用一棵树来表示。

技术实现:游戏AI使用决策树或极小化极大算法(Minimax)来评估不同的行动方案。

应用

  • 国际象棋、围棋等棋类游戏的AI
  • 策略游戏中的电脑对手
  • 角色扮演游戏中的NPC行为决策
6. HTML/XML文档的DOM树

生活例子:网页的结构,从最外层的HTML标签开始,包含头部、主体等部分,每个部分又包含更小的元素。

<html>
  <head>
    <title>我的网页</title>
  </head>
  <body>
    <h1>欢迎</h1>
    <p>这是一个<a href="#">链接</a></p>
  </body>
</html>

这段HTML代码可以表示为以下树结构:

       html
       /  \
    head   body
     |     /  \
   title   h1   p
            |    \
         "欢迎"   a
               /
            "链接"

应用

  • 网页浏览器的渲染引擎
  • 前端JavaScript框架(如React、Vue)
  • XML解析器
7. 家谱树

生活例子:家族的血缘关系图,从祖先开始,分支到子女,再到孙辈等。

应用

  • 家谱研究软件
  • 遗传学研究
  • 社交网络中的关系图
8. 编译器的语法分析树

生活例子:理解一个复杂的句子,我们会分析其主语、谓语、宾语等成分,这个过程类似于编译器构建语法树。

技术实现:编译器将源代码解析为语法树,然后进行语义分析和代码生成。

应用

  • 各种编程语言的编译器
  • 自然语言处理
  • 表达式求值
9. 路由算法

生活例子:导航软件寻找从起点到终点的最佳路线,考虑各种可能的路径。

技术实现:路由算法使用树结构来表示可能的路径,并使用诸如Dijkstra或A*等算法来找到最短路径。

应用

  • GPS导航系统
  • 网络路由
  • 游戏中的寻路算法
10. 压缩算法

生活例子:摩尔斯电码根据字符出现的频率分配不同长度的编码,常用字符使用较短的编码。

技术实现:霍夫曼编码使用二叉树来为字符分配变长编码,实现数据压缩。

应用

  • 文件压缩软件(如ZIP、GZIP)
  • 图像压缩(如JPEG)
  • 视频压缩(如MPEG)