深入研究树算法:从原理到生产实践

1. 引言

树是计算机科学中最基础且最重要的数据结构之一。从文件系统的目录树到数据库的 B+ 树索引,从编译器的语法树到机器学习的决策树——树结构无处不在。

本文将从原理图示代码三个维度,深入探讨几种核心树算法,并结合实际生产场景分析它们的应用。


2. 基础二叉树

2.1 什么是二叉树?

二叉树是一种递归定义的数据结构:

  • 要么为空(空树)
  • 要么由一个根节点和两个不相交的子树(左子树和右子树)组成

结构示意:

graph TD
    A((A)):::root --> B((B))
    A --> C((C))
    B --> D((D)):::leaf
    B --> E((E)):::leaf
    C --> F((F)):::leaf
    
    classDef root fill:#e1f5fe,stroke:#01579b,stroke-width:3px
    classDef leaf fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px

🔵 蓝色 = 根节点,🟢 绿色 = 叶子节点

核心概念:

  • 深度:从根到某节点的边数(根节点深度为 0)
  • 高度:从某节点到最远叶子节点的边数
  • 满二叉树:每个内部节点都有两个子节点
  • 完全二叉树:除最后一层外,每层节点都满,且最后一层从左到右填充

2.2 为什么需要不同的遍历方式?

不同遍历顺序对应不同的应用场景:

表达式树示例:(3 + 4) * 5

graph TD
    M((*)):::op --> P((+)):::op
    M --> N5((5)):::num
    P --> N3((3)):::num
    P --> N4((4)):::num
    
    classDef op fill:#ffecb3,stroke:#ff6f00,stroke-width:2px
    classDef num fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
遍历方式 结果 用途
前序遍历 * + 3 4 5 前缀表达式(波兰表示法)
中序遍历 3 + 4 * 5 中缀表达式(人类可读)
后序遍历 3 4 + 5 * 后缀表达式(便于计算)

2.3 遍历算法原理

前序遍历(DFS):先访问根,再递归访问左子树、右子树

graph TD
    A1((A<br/>①)):::order --> B1((B<br/>②)):::order
    A1 --> C1((C<br/>⑤)):::order
    B1 --> D1((D<br/>③)):::order
    B1 --> E1((E<br/>④)):::order
    C1 --> F1((F<br/>⑥)):::order
    
    classDef order fill:#fff3e0,stroke:#e65100,stroke-width:2px

访问顺序:A → B → D → E → C → F

中序遍历:先递归左子树,再访问根,最后递归右子树

对于 BST,中序遍历得到有序序列

graph TD
    R4((4)):::bst --> R2((2)):::bst
    R4 --> R8((8)):::bst
    R2 --> R1((1)):::bst
    R8 --> R6((6)):::bst
    R8 --> R10((10)):::bst
    
    classDef bst fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px

中序:1 → 2 → 4 → 6 → 8 → 10(有序!)

2.4 代码实现

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

# 递归实现
def preorder(root):
    """前序:根 → 左 → 右"""
    if not root:
        return []
    return [root.val] + preorder(root.left) + preorder(root.right)

def inorder(root):
    """中序:左 → 根 → 右"""
    if not root:
        return []
    return inorder(root.left) + [root.val] + inorder(root.right)

def postorder(root):
    """后序:左 → 右 → 根"""
    if not root:
        return []
    return postorder(root.left) + postorder(root.right) + [root.val]

# 迭代实现(使用显式栈)
def preorder_iterative(root):
    result, stack = [], [root] if root else []
    while stack:
        node = stack.pop()
        result.append(node.val)
        if node.right: stack.append(node.right)
        if node.left: stack.append(node.left)
    return result

def inorder_iterative(root):
    """中序遍历迭代版"""
    result, stack = [], []
    curr = root
    while curr or stack:
        while curr:
            stack.append(curr)
            curr = curr.left
        curr = stack.pop()
        result.append(curr.val)
        curr = curr.right
    return result

复杂度分析:

  • 时间复杂度:O(n),每个节点访问一次
  • 空间复杂度:O(h),h 为树高(递归栈或显式栈)

3. 二叉搜索树(BST)

3.1 核心性质

BST 满足以下性质:

  • 左子树所有节点值 < 根节点值
  • 右子树所有节点值 > 根节点值
  • 左右子树也都是 BST
graph TD
    N8((8)):::node --> N3((3)):::node
    N8 --> N10((10)):::node
    N3 --> N1((1)):::node
    N3 --> N6((6)):::node
    N6 --> N4((4)):::node
    N6 --> N7((7)):::node
    N10 --> N14((14)):::node
    N14 --> N13((13)):::node
    
    classDef node fill:#e3f2fd,stroke:#1565c0,stroke-width:2px

✅ 有效 BST:每个节点的左子树都小于它,右子树都大于它

3.2 操作原理

查找操作:从根开始,比目标小往左走,比目标大往右走

graph TD
    A8((8)):::visited --> A3((3)):::visited
    A8 --> A10((10))
    A3 --> A1((1))
    A3 --> A6((6)):::found
    A6 --> A4((4))
    A6 --> A7((7))
    
    classDef visited fill:#fff3e0,stroke:#e65100,stroke-width:2px
    classDef found fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px

查找 6:8 → 3 → 6(3 步)

插入操作流程:

flowchart LR
    A[开始] --> B{与当前节点比较}
    B -->|小于| C{左子节点存在?}
    B -->|大于| D{右子节点存在?}
    C -->|是| E[移到左子节点]
    C -->|否| F[插入左子节点]
    D -->|是| G[移到右子节点]
    D -->|否| H[插入右子节点]
    E --> B
    G --> B
    F --> I[结束]
    H --> I

删除操作三种情况:

flowchart TD
    A[删除节点] --> B{子节点数量}
    B -->|0个| C[直接删除]
    B -->|1个| D[用子节点替换]
    B -->|2个| E[找后继节点替换]
    E --> F[复制后继值]
    F --> G[删除后继节点]

3.3 复杂度分析

操作 平均情况 最坏情况
查找 O(log n) O(n)
插入 O(log n) O(n)
删除 O(log n) O(n)

最坏情况:树退化为链表

graph LR
    A((1)) --> B((2))
    B --> C((3))
    C --> D((4))
    D --> E((5))
    E --> F((6))
    
    style A fill:#ffcdd2,stroke:#c62828
    style B fill:#ffcdd2,stroke:#c62828
    style C fill:#ffcdd2,stroke:#c62828
    style D fill:#ffcdd2,stroke:#c62828
    style E fill:#ffcdd2,stroke:#c62828
    style F fill:#ffcdd2,stroke:#c62828

⚠️ 退化的 BST 查找 6 需要 6 步,复杂度 O(n)

3.4 代码实现

class BST:
    def __init__(self):
        self.root = None
    
    def search(self, val):
        """迭代查找"""
        curr = self.root
        while curr:
            if val == curr.val:
                return curr
            curr = curr.left if val < curr.val else curr.right
        return None
    
    def insert(self, val):
        """迭代插入"""
        if not self.root:
            self.root = TreeNode(val)
            return
        curr = self.root
        while True:
            if val < curr.val:
                if not curr.left:
                    curr.left = TreeNode(val)
                    return
                curr = curr.left
            else:
                if not curr.right:
                    curr.right = TreeNode(val)
                    return
                curr = curr.right
    
    def delete(self, val):
        self.root = self._delete_node(self.root, val)
    
    def _delete_node(self, node, val):
        if not node:
            return None
        if val < node.val:
            node.left = self._delete_node(node.left, val)
        elif val > node.val:
            node.right = self._delete_node(node.right, val)
        else:
            if not node.left:
                return node.right
            if not node.right:
                return node.left
            successor = self._min_node(node.right)
            node.val = successor.val
            node.right = self._delete_node(node.right, successor.val)
        return node
    
    def _min_node(self, node):
        while node.left:
            node = node.left
        return node

4. 平衡二叉搜索树

4.1 为什么需要平衡?

普通 BST 在数据有序时会退化为链表,导致操作效率下降到 O(n)。

平衡树通过旋转操作,保证树的高度始终在 O(log n) 级别。

4.2 AVL 树

平衡因子 = 左子树高度 - 右子树高度

AVL 树要求:|平衡因子| ≤ 1

平衡的 AVL 树:

graph TD
    AV8((8)):::balanced --> AV4((4)):::balanced
    AV8 --> AV12((12)):::balanced
    AV4 --> AV2((2)):::balanced
    AV4 --> AV6((6)):::balanced
    AV12 --> AV14((14)):::balanced
    
    classDef balanced fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px

不平衡的树:

graph TD
    UB8((8)):::unbalanced --> UB4((4)):::unbalanced
    UB4 --> UB2((2)):::unbalanced
    
    classDef unbalanced fill:#ffcdd2,stroke:#c62828,stroke-width:2px

节点 8 的平衡因子 = +2,不平衡!

四种旋转修复:

graph LR
    subgraph LL型[LL型:右旋]
        LL1[Z] --> LL2[Y]
        LL2 --> LL3[X]
    end
    
    subgraph LL结果[结果]
        LR1[Y] --> LR2[X]
        LR1 --> LR3[Z]
    end
    
    LL型 -->|右旋| LL结果
graph LR
    subgraph RR型[RR型:左旋]
        RR1[Z] --> RR2[Y]
        RR2 --> RR3[X]
    end
    
    subgraph RR结果[结果]
        RRr1[Y] --> RRr2[Z]
        RRr1 --> RRr3[X]
    end
    
    RR型 -->|左旋| RR结果

旋转类型判断:

flowchart TD
    A[检测到不平衡] --> B{左子树高还是右子树高}
    B -->|左子树高| C{插入点在左子树的哪边}
    B -->|右子树高| D{插入点在右子树的哪边}
    C -->|左边| E[LL型:右旋]
    C -->|右边| F[LR型:先左旋再右旋]
    D -->|右边| G[RR型:左旋]
    D -->|左边| H[RL型:先右旋再左旋]

4.3 AVL 代码实现

class AVLNode:
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None
        self.height = 1

class AVLTree:
    def height(self, node):
        return node.height if node else 0
    
    def balance_factor(self, node):
        return self.height(node.left) - self.height(node.right) if node else 0
    
    def update_height(self, node):
        node.height = 1 + max(self.height(node.left), self.height(node.right))
    
    def rotate_right(self, y):
        """右旋:用于 LL 型"""
        x = y.left
        T2 = x.right
        x.right = y
        y.left = T2
        self.update_height(y)
        self.update_height(x)
        return x
    
    def rotate_left(self, x):
        """左旋:用于 RR 型"""
        y = x.right
        T2 = y.left
        y.left = x
        x.right = T2
        self.update_height(x)
        self.update_height(y)
        return y
    
    def insert(self, node, val):
        if not node:
            return AVLNode(val)
        if val < node.val:
            node.left = self.insert(node.left, val)
        else:
            node.right = self.insert(node.right, val)
        
        self.update_height(node)
        balance = self.balance_factor(node)
        
        # LL 型
        if balance > 1 and val < node.left.val:
            return self.rotate_right(node)
        # RR 型
        if balance < -1 and val > node.right.val:
            return self.rotate_left(node)
        # LR 型
        if balance > 1 and val > node.left.val:
            node.left = self.rotate_left(node.left)
            return self.rotate_right(node)
        # RL 型
        if balance < -1 and val < node.right.val:
            node.right = self.rotate_right(node.right)
            return self.rotate_left(node)
        
        return node

4.4 红黑树简介

红黑树是另一种平衡树,在 Java TreeMap、C++ std::map 中广泛使用。

五条性质:

  1. 每个节点是红色或黑色
  2. 根节点是黑色
  3. 叶子节点(NIL)是黑色
  4. 红色节点的子节点必须是黑色(不能连续红)
  5. 从任一节点到其叶子的所有路径,黑色节点数量相同
graph TD
    RB10((10)):::black --> RB5((5)):::red
    RB10 --> RB15((15)):::red
    RB5 --> RB3((3)):::black
    RB5 --> RB7((7)):::black
    RB15 --> RB12((12)):::black
    RB15 --> RB20((20)):::black
    
    classDef black fill:#424242,stroke:#212121,stroke-width:2px,color:#fff
    classDef red fill:#ef5350,stroke:#b71c1c,stroke-width:2px,color:#fff

特点:通过颜色规则,保证最长路径不超过最短路径的 2 倍


5. B 树与 B+ 树

5.1 为什么需要 B 树?

BST 的问题是每个节点只存一个值,树很高。对于磁盘存储,每次访问一个节点可能需要一次磁盘 I/O,树越高,I/O 次数越多。

B 树是"矮胖"的多路树,每个节点存储多个键,减少树的高度。

5.2 B 树结构

m 阶 B 树的性质:

  • 每个节点最多 m 个子节点,m-1 个键
  • 根节点至少 2 个子节点
  • 非根非叶节点至少 ⌈m/2⌉ 个子节点
  • 所有叶子节点在同一层

3 阶 B 树示例:

graph TD
    subgraph Level1[根节点]
        ROOT["[30 | 70]"]
    end
    
    subgraph Level2[叶子层]
        L1["[10 | 20]"]
        L2["[50 | 60]"]
        L3["[80 | 90]"]
    end
    
    ROOT --> L1
    ROOT --> L2
    ROOT --> L3
    
    style ROOT fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    style L1 fill:#fff3e0,stroke:#e65100,stroke-width:2px
    style L2 fill:#fff3e0,stroke:#e65100,stroke-width:2px
    style L3 fill:#fff3e0,stroke:#e65100,stroke-width:2px

高度 2,最多存储 3² - 1 = 8 个键

5.3 B+ 树 vs B 树

graph TD
    subgraph B树["B 树(数据在所有节点)"]
        B1["[30 | 70] + 数据"]
        B2["[10] + 数据"]
        B3["[50] + 数据"]
        B4["[80] + 数据"]
        B1 --> B2
        B1 --> B3
        B1 --> B4
    end
    
    subgraph B+树["B+ 树(数据只在叶子)"]
        BP1["[30 | 70] 索引"]
        BP2["[10 | 20] 数据"]
        BP3["[50 | 60] 数据"]
        BP4["[80 | 90] 数据"]
        BP1 --> BP2
        BP1 --> BP3
        BP1 --> BP4
        BP2 <-."链表连接".-> BP3 <-."链表连接".-> BP4
    end

B+ 树优势:

  1. 范围查询:只需遍历叶子链表
  2. 更多索引:非叶节点不存数据,可存更多索引

5.4 生产场景:MySQL InnoDB

graph LR
    subgraph 性能估算
        A["页大小: 16KB"]
        B["主键: 8字节"]
        C["指针: 6字节"]
        D["每页索引: ~1170"]
        E["3层容量: ~2000万行"]
        F["查询: 最多3次I/O"]
    end
    
    A --> D
    B --> D
    C --> D
    D --> E
    E --> F
    
    style F fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px

6. 字典树(Trie)

6.1 原理

Trie 是专门用于字符串存储和查找的树结构。

核心思想:用边代表字符,从根到某节点的路径代表一个字符串。

graph TD
    ROOT((root)) --> A(a)
    ROOT --> B(b)
    A --> P1(p)
    P1 --> P2(p✓)
    P2 --> L1(l)
    L1 --> E(e✓)
    L1 --> Y(y✓)
    B --> A2(a)
    A2 --> T(t✓)
    A2 --> L2(l)
    L2 --> L3(l✓)
    
    style ROOT fill:#e0e0e0,stroke:#616161
    style P2 fill:#c8e6c9,stroke:#2e7d32
    style E fill:#c8e6c9,stroke:#2e7d32
    style Y fill:#c8e6c9,stroke:#2e7d32
    style T fill:#c8e6c9,stroke:#2e7d32
    style L3 fill:#c8e6c9,stroke:#2e7d32

存储单词:{app✓, apple✓, apply✓, bat✓, ball✓}
✓ = 单词结束标记

查找过程:

flowchart LR
    A["查找 'apple'"] --> B["a → p → p → l → e"]
    B --> C{有结束标记?}
    C -->|是| D["✓ 找到"]
    C -->|否| E["✗ 不存在"]

6.2 复杂度分析

操作 时间复杂度 说明
插入 O(m) m 为字符串长度
查找 O(m) m 为字符串长度
前缀匹配 O(m + k) k 为匹配结果数

与哈希表相比:

  • ✅ Trie 支持前缀查找、按字典序遍历
  • ✅ Trie 不需要处理哈希冲突

6.3 代码实现

class TrieNode:
    def __init__(self):
        self.children = {}  # 字符 → 子节点
        self.is_end = False  # 是否为单词结尾

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word):
        """插入单词:O(m)"""
        node = self.root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
        node.is_end = True
    
    def search(self, word):
        """精确查找:O(m)"""
        node = self.root
        for c in word:
            if c not in node.children:
                return False
            node = node.children[c]
        return node.is_end
    
    def starts_with(self, prefix):
        """前缀是否存在:O(m)"""
        node = self.root
        for c in prefix:
            if c not in node.children:
                return False
            node = node.children[c]
        return True

6.4 应用:敏感词过滤

flowchart TD
    A[输入文本] --> B[遍历文本]
    B --> C{当前字符在Trie中?}
    C -->|是| D[继续匹配]
    C -->|否| E[保留原字符]
    D --> F{到达单词结尾?}
    F -->|是| G[替换为*]
    F -->|否| C
    G --> B
    E --> B
    B -->|结束| H[输出过滤后文本]

7. 线段树

7.1 原理

线段树用于解决区间问题,支持 O(log n) 的区间查询和单点更新。

核心思想:将区间递归二分,每个节点存储一个区间的聚合值(和、最值等)。

数组 [1, 3, 5, 7, 9, 11] 的线段树:

graph TD
    ROOT["[0-5]: 36"] --> L1["[0-2]: 9"]
    ROOT --> R1["[3-5]: 27"]
    L1 --> L2["[0-1]: 4"]
    L1 --> R2["[2]: 5"]
    R1 --> L3["[3-4]: 16"]
    R1 --> R3["[5]: 11"]
    L2 --> L4["[0]: 1"]
    L2 --> R4["[1]: 3"]
    L3 --> L5["[3]: 7"]
    L3 --> R5["[4]: 9"]
    
    style ROOT fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    style L1 fill:#fff3e0,stroke:#e65100,stroke-width:2px
    style R1 fill:#fff3e0,stroke:#e65100,stroke-width:2px

查询区间 [2, 4] 的和:

graph TD
    ROOT["[0-5]: 36"] --> L1["[0-2]: 9"]
    ROOT --> R1["[3-5]: 27"]
    L1 --> L2["[0-1]: 4"]
    L1 --> R2["[2]: 5"]:::used
    R1 --> L3["[3-4]: 16"]:::used
    R1 --> R3["[5]: 11"]
    
    classDef used fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px

查询结果 = [2]:5 + [3-4]:16 = 21(只需访问 2 个节点!)

7.2 复杂度

操作 时间复杂度 空间复杂度
构建 O(n) O(4n)
单点更新 O(log n) -
区间查询 O(log n) -

7.3 代码实现

class SegmentTree:
    def __init__(self, data):
        self.n = len(data)
        self.tree = [0] * (4 * self.n)
        self._build(data, 0, 0, self.n - 1)
    
    def _build(self, data, node, l, r):
        if l == r:
            self.tree[node] = data[l]
        else:
            mid = (l + r) // 2
            self._build(data, 2*node+1, l, mid)
            self._build(data, 2*node+2, mid+1, r)
            self.tree[node] = self.tree[2*node+1] + self.tree[2*node+2]
    
    def query(self, ql, qr):
        return self._query(0, 0, self.n-1, ql, qr)
    
    def _query(self, node, l, r, ql, qr):
        if qr < l or ql > r:
            return 0
        if ql <= l and r <= qr:
            return self.tree[node]
        mid = (l + r) // 2
        return self._query(2*node+1, l, mid, ql, qr) + \
               self._query(2*node+2, mid+1, r, ql, qr)

8. 决策树

8.1 原理

决策树通过递归选择最优特征进行分裂,将数据划分成越来越"纯"的子集。

贷款审批决策树:

graph TD
    A["收入?"] -->|<5万| B["负债?"]
    A -->|≥5万| C["信用?"]
    B -->|高| D["❌ 拒绝"]:::reject
    B -->|低| E["✓ 通过"]:::approve
    C -->|差| F["❌ 拒绝"]:::reject
    C -->|好| G["✓ 通过"]:::approve
    
    classDef reject fill:#ffcdd2,stroke:#c62828,stroke-width:2px
    classDef approve fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    
    style A fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    style B fill:#fff3e0,stroke:#e65100,stroke-width:2px
    style C fill:#fff3e0,stroke:#e65100,stroke-width:2px

8.2 信息熵与信息增益

graph LR
    subgraph 信息熵
        E1["[是,是,是,是]<br/>熵 = 0<br/>(完全确定)"]:::certain
        E2["[是,是,否,否]<br/>熵 = 1<br/>(最不确定)"]:::uncertain
    end
    
    classDef certain fill:#c8e6c9,stroke:#2e7d32
    classDef uncertain fill:#ffcdd2,stroke:#c62828

信息增益计算:

信息增益 = 父节点熵 - Σ(子节点权重 × 子节点熵)

选择信息增益最大的特征进行分裂

9. 如何选择树结构

graph TD
    START["选择树结构"] --> Q1{存储位置?}
    Q1 -->|内存| Q2{需要范围查询?}
    Q1 -->|磁盘| Q3{需要范围查询?}
    
    Q2 -->|是| RB["红黑树<br/>O(log n)"]
    Q2 -->|否| HT["哈希表"]
    
    Q3 -->|是| BPT["B+ 树<br/>O(log n)"]
    Q3 -->|否| LSM["LSM Tree"]
    
    START --> Q4{特殊需求?}
    Q4 -->|字符串前缀| TRIE["Trie<br/>O(m)"]
    Q4 -->|区间查询| SEG["线段树<br/>O(log n)"]
    Q4 -->|分类任务| DT["决策树<br/>O(n log n)"]
    
    style RB fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    style BPT fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    style TRIE fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    style SEG fill:#fff3e0,stroke:#e65100,stroke-width:2px
    style DT fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
需求 推荐结构 时间复杂度 典型应用
内存键值存储 红黑树 O(log n) Java TreeMap, C++ std::map
磁盘持久化 B+ 树 O(log n) MySQL InnoDB, PostgreSQL
字符串前缀匹配 Trie O(m) 搜索提示、敏感词过滤
区间聚合查询 线段树 O(log n) 实时监控、游戏排行榜
分类/回归任务 XGBoost/LightGBM O(n log n) 风控系统、推荐系统

工程优化建议

  1. 内存对齐:树节点大小应对齐到缓存行(64 字节)
  2. 内存池:高频分配场景使用预分配池
  3. 批量操作:B+ 树批量插入时先排序再构建
  4. 并发控制:读多写少用 Copy-on-Write,写多用细粒度锁

10. 总结

树算法是计算机科学的核心基础设施:

mindmap
  root((树算法应用))
    操作系统
      进程调度 红黑树
      内存管理
    数据库
      索引结构 B+树
      查询优化
    编译器
      语法分析 AST
      代码生成
    机器学习
      特征选择 决策树
      模型构建
    搜索系统
      自动补全 Trie
      全文检索

深入理解树的原理和实现,是构建高性能系统的必备能力。


参考资料

  1. Cormen, T. H. et al. Introduction to Algorithms, 4th Edition
  2. MySQL 8.0 Reference Manual - InnoDB Storage Engine
  3. LightGBM: A Highly Efficient Gradient Boosting Decision Tree
  4. Linux Kernel Documentation - Red-Black Trees
  5. Database System Concepts - Silberschatz et al.