b树详细介绍与示例

B 树是一棵多路平衡查找树。每个节点可以放多个有序关键字,所有叶子都在同一层。数据库索引和文件系统用它,是因为一个节点正好对应磁盘上的一页:一次读入就能比较多个关键字,树变矮,磁盘次数就少。

下面用最小度数 t = 2 举例。这是教学里最常见的一种,也叫 2-3-4 树。

  • 一个节点最多 2t - 1 = 3 个关键字。
  • 除根以外,每个节点至少 t - 1 = 1 个关键字。
  • 有 k 个关键字的内部节点有 k + 1 个子节点。
  • 关键字从小到大排列。第 i 个子树里的值,都落在第 i-1 个和第 i 个关键字之间。

插入时怎么分裂

依次插入 10, 20, 5, 6, 12, 30, 7, 17。

节点没满时,只是按顺序插进去:

复制代码
[10] → [10, 20] → [5, 10, 20]

再插入 6 时,叶子已经有 3 个关键字,必须先分裂。中间的 10 升到父节点,左右各留一半:

复制代码
      [10]
     /    \
[5, 6]    [20]

12、30、7 都还能放进现有叶子。插入 17 时,右叶子 [12, 20, 30] 已满,中间的 20 升上去:

复制代码
         [10, 20]
       /    |     \
[5, 6, 7] [12, 17] [30]

查找 12:根上 10 < 12 < 20,走进中间那棵子树,在 [12, 17] 里命中。查找 99:从根走到最右边的 [30],叶子里没有,就确定不存在。整棵树的高度始终一致,不会像普通二叉搜索树那样一边倒。

删除时要维持"至少 t-1 个关键字"。节点不够就向兄弟借一个;兄弟也不够,就和兄弟以及父节点里的分隔关键字合并。插入的核心是满了就分裂,删除的核心是不够就借或合并。

复制代码
class BTreeNode:
    def __init__(self, leaf=True):
        self.leaf = leaf
        self.keys = []
        self.children = []


class BTree:
    def __init__(self, t):
        if t < 2:
            raise ValueError("t 必须 >= 2")
        self.t = t
        self.root = BTreeNode(leaf=True)

    def search(self, key, node=None):
        node = self.root if node is None else node
        i = 0
        while i < len(node.keys) and key > node.keys[i]:
            i += 1
        if i < len(node.keys) and key == node.keys[i]:
            return node, i
        if node.leaf:
            return None
        return self.search(key, node.children[i])

    def insert(self, key):
        root = self.root
        if len(root.keys) == 2 * self.t - 1:
            new_root = BTreeNode(leaf=False)
            new_root.children.append(root)
            self._split_child(new_root, 0)
            self.root = new_root
        self._insert_non_full(self.root, key)

    def _insert_non_full(self, node, key):
        i = len(node.keys) - 1
        if node.leaf:
            node.keys.append(None)
            while i >= 0 and key < node.keys[i]:
                node.keys[i + 1] = node.keys[i]
                i -= 1
            node.keys[i + 1] = key
            return
        while i >= 0 and key < node.keys[i]:
            i -= 1
        i += 1
        if len(node.children[i].keys) == 2 * self.t - 1:
            self._split_child(node, i)
            if key > node.keys[i]:
                i += 1
        self._insert_non_full(node.children[i], key)

    def _split_child(self, parent, i):
        t = self.t
        full = parent.children[i]
        right = BTreeNode(leaf=full.leaf)
        mid = full.keys[t - 1]
        right.keys = full.keys[t:]
        full.keys = full.keys[: t - 1]
        if not full.leaf:
            right.children = full.children[t:]
            full.children = full.children[:t]
        parent.keys.insert(i, mid)
        parent.children.insert(i + 1, right)


tree = BTree(2)
for k in [10, 20, 5, 6, 12, 30, 7, 17]:
    tree.insert(k)

print(tree.search(12) is not None)  # True
print(tree.search(99))               # None

数据库里更常见的是 B+ 树:内部节点只放索引用的关键字,真正的数据都在叶子,叶子之间再串成链表,方便按范围往后扫。

B 树和它的变体 B+ 树,主要用在「按关键字找一页数据,还要经常做范围扫描」的地方。一个节点对应磁盘或 SSD 上的一页,树矮,随机读的次数少。

数据库索引是现在最常见的用途。

  • MySQL InnoDB 的聚簇索引和二级索引都是 B+ 树,表数据就挂在主键索引的叶子上。
  • PostgreSQL 默认的 btree 索引、Oracle 的 B-tree 索引、SQL Server 的聚集/非聚集索引,结构都是 B+ 树。
  • SQLite 的表和索引也是 B 树。
  • MongoDB 的 WiredTiger 用 B+ 树存集合和索引。

文件系统的目录和元数据也大量用它。

  • XFS、NTFS、APFS 用 B+ 树管目录和分配信息。
  • btrfs 的核心结构就是写时复制的 B 树。
  • ext4 的大目录用 HTree,是按哈希分桶的 B 树。

嵌入式键值库里,LMDB、Berkeley DB 以 B+ 树做主结构。读多、数据要落在本地文件、还要范围查询时,这种结构很合适。

写非常多、可以接受后台合并的系统,常见主结构是 LSM-Tree,例如 RocksDB、LevelDB、Cassandra、HBase。分析型引擎如 StarRocks、Doris 的主键模型也不是用 B 树存整表,而是列存加上稀疏前缀索引。在线事务库的索引和文件系统元数据,目前仍然以 B+ 树为主。

相关推荐
老师我太想进步了20263 小时前
【数据结构-3】算法美学之降复杂度
数据结构·算法
w_zero_one15 小时前
算法--构造
java·数据结构·算法
lisw0519 小时前
互联网上的数据结构可视化!
数据结构·信息可视化
老师我太想进步了20261 天前
【数据结构-1】例解算法复杂度
数据结构·算法
YSL0701241 天前
一文通关-----排序
数据结构·算法
夜不会漫长2 天前
C++:红黑树
数据结构·算法
冻柠檬飞冰走茶2 天前
PTA刷题集之Airline Routes(C语言实现)
c语言·开发语言·数据结构·算法
736c2 天前
固件开发岗算法
数据结构·算法
冻柠檬飞冰走茶2 天前
PTA刷题集之Battle Over Cities - Hard Version(C语言实现)
c语言·开发语言·数据结构·算法
PaperAccept2 天前
【研知有术论文发表】材料科学一区期刊,影响因子30+的纳米科技SCI,审稿严格但有效率
大数据·数据结构·科技·量子计算