一句话说透数据结构里面的什么是红黑树,为什么要用红黑树?

184 阅读3分钟

一句话总结:
红黑树就像一个有严格家规的二叉搜索树——通过颜色标记和旋转操作保持「相对平衡」,防止退化成瘸腿链表,让查找、插入、删除都能在 O(log n) 时间内搞定!


一、红黑树的「家规」

红黑树必须遵守以下五条规则:

  1. 颜色规则:每个节点非红即黑
  2. 根节点规则:根必须是黑
  3. 叶子规则:所有叶子(NIL节点)都是黑
  4. 红节点规则:红节点的子节点必须全黑(不能有连续红)
  5. 黑高规则:从任一节点到其叶子的路径,黑节点数量相同

违反家规的后果:通过旋转和变色重新平衡(后文代码演示)


二、为什么用红黑树?

普通二叉搜索树(BST)在插入有序数据时会退化成链表(查询变 O(n)),而红黑树通过自平衡机制:

  1. 保证平衡:最坏情况下也能保持 O(log n) 的查询效率
  2. 插入/删除高效:比 AVL 树宽松的平衡条件,减少旋转次数
  3. 广泛实用:适合频繁插入删除的场景(如 Linux 内核调度、Java 的 TreeMap)

对比其他结构

数据结构插入/删除查找平衡性要求
普通 BST可能退化成 O(n)可能退化成 O(n)
红黑树O(log n)O(log n)相对平衡
AVL 树O(log n)O(log n)严格平衡

三、Kotlin 代码实现关键部分

1. 红黑树节点定义

private enum class Color { RED, BLACK }

class RBNode<T : Comparable<T>>(
    var data: T,
    var color: Color = Color.RED, // 新节点默认红色(便于调整)
    var parent: RBNode<T>? = null,
    var left: RBNode<T>? = null,
    var right: RBNode<T>? = null
)

2. 插入后修复平衡(核心!)

private fun fixAfterInsertion(node: RBNode<T>) {
    var current = node
    while (current.parent?.color == Color.RED) { // 父节点是红,需要调整
        val parent = current.parent!!
        val grandParent = parent.parent!!
        
        if (parent == grandParent.left) { // 父节点是祖父的左孩子
            val uncle = grandParent.right
            if (uncle?.color == Color.RED) { // 情况1:叔叔是红
                // 父和叔变黑,祖父变红,current上移到祖父
                parent.color = Color.BLACK
                uncle.color = Color.BLACK
                grandParent.color = Color.RED
                current = grandParent
            } else { 
                if (current == parent.right) { // 情况2:current是右孩子
                    // 左旋父节点,转化为情况3
                    current = parent
                    rotateLeft(current)
                }
                // 情况3:current是左孩子
                parent.color = Color.BLACK
                grandParent.color = Color.RED
                rotateRight(grandParent)
            }
        } else { // 对称处理(父节点是祖父的右孩子)
            // 类似上述代码,左右旋转方向相反
        }
    }
    root?.color = Color.BLACK // 确保根节点为黑
}

3. 左旋操作(右旋对称)

private fun rotateLeft(node: RBNode<T>) {
    val rightChild = node.right!!
    // 处理node的右子树的左孩子
    node.right = rightChild.left
    rightChild.left?.parent = node
    
    // 处理node的父节点
    rightChild.parent = node.parent
    if (node.parent == null) {
        root = rightChild
    } else if (node == node.parent!!.left) {
        node.parent!!.left = rightChild
    } else {
        node.parent!!.right = rightChild
    }
    
    // 更新node和右孩子的父子关系
    rightChild.left = node
    node.parent = rightChild
}

四、红黑树的实际应用场景

  1. 数据库索引:B+树的变种常用于数据库,但红黑树适合内存索引
  2. 语言标准库:Java的 TreeMap、C++的 std::map
  3. 实时系统:保证最坏情况下的操作时间
  4. 文件系统:如 Ext3 的目录索引

五、红黑树 vs 哈希表

特性红黑树哈希表
有序性支持范围查询(中序遍历)无序
最坏时间复杂度O(log n)O(n)(哈希冲突时)
内存开销较高(存储颜色/指针)较低

六、总结口诀

红黑树,真强悍,
五条家规保平安。
红不连,黑同高,
旋转变色调平衡。

查删插,对数阶,
不怕数据顺序来。
若问哪里用它好?
有序操作快稳牢!