7.4考研408数据结构B树与B+树专题深度解析

考研408数据结构B树与B+树专题深度解析

一、B树(B-Tree)

1.1 定义与性质

定义
B树是一种平衡多路查找树,满足以下条件:

  1. 阶数:每个结点最多有 m m m个子树( m ≥ 3 m \geq 3 m3),称为 m m m阶B树
  2. 关键字数量
    • 根结点: 1 ≤ n ≤ m − 1 1 \leq n \leq m-1 1nm1
    • 非根非叶结点: ⌈ m / 2 ⌉ − 1 ≤ n ≤ m − 1 \lceil m/2 \rceil -1 \leq n \leq m-1 m/21nm1
  3. 结构特性
    • 所有叶结点位于同一层(绝对平衡)
    • 结点内关键字升序排列,满足子树指针分割关系: A i − 1 A_{i-1} Ai1子树所有关键字 < K i < A i <Ki<Ai子树所有关键字

核心性质

  • 平衡性:所有叶结点高度相同
  • 查找效率:查找复杂度为 O ( log ⁡ m N ) O(\log_m N) O(logmN),树高 h h h满足 h ≤ log ⁡ ⌈ m / 2 ⌉ N + 1 2 + 1 h \leq \log_{\lceil m/2 \rceil} \frac{N+1}{2}+1 hlogm/22N+1+1

易错点

  1. 阶数理解误区:混淆“阶”与“度数”,误认为每个结点必须有 m m m个子树(实际非根结点最少为 ⌈ m / 2 ⌉ \lceil m/2 \rceil m/2
  2. 关键字数量计算:忘记非根结点最小关键字数为 ⌈ m / 2 ⌉ − 1 \lceil m/2 \rceil -1 m/21,导致插入删除操作错误
  3. 平衡调整:未正确处理分裂/合并时父结点关键字更新,导致树结构失衡

1.2 基本操作与实现

1.2.1 插入操作

算法步骤

  1. 查找插入位置:从根结点开始,按关键字大小选择子树路径
  2. 插入关键字:在叶结点或非叶结点插入新关键字,保持有序性
  3. 分裂调整:若结点关键字数超过 m − 1 m-1 m1,则分裂为两个结点,中间关键字上升到父结点
  4. 递归调整:若父结点因分裂超限,继续向上分裂直至根结点

实现代码(伪代码)

void BTreeInsert(BTree *T, KeyType k) {
    if (T == NULL) return;
    if (T->keynum < m-1) {  // 直接插入
        InsertKey(T, k);
    } else {  // 分裂
        BTreeNode *newNode = SplitNode(T);
        KeyType midKey = T->keys[⌈m/2];
        InsertKey(parent, midKey);  // 中间关键字插入父结点
        // 递归处理父结点
    }
}

难点

  • 分裂策略:中间关键字选择( ⌈ m / 2 ⌉ \lceil m/2 \rceil m/2位置)
  • 父结点更新:分裂后需正确调整父结点指针与关键字顺序

1.2.2 删除操作

算法步骤

  1. 查找删除位置:定位关键字所在结点
  2. 删除关键字
    • 叶结点:直接删除
    • 非叶结点:用前驱/后继替代(左子树最大或右子树最小)
  3. 调整平衡
    • 借关键字:若兄弟结点关键字数 > ⌈ m / 2 ⌉ − 1 > \lceil m/2 \rceil -1 >m/21,则向兄弟借
    • 合并结点:若兄弟不足以借,合并当前结点与兄弟,父结点关键字下移

实现代码(伪代码)

void BTreeDelete(BTree *T, KeyType k) {
    Node *target = SearchNode(T, k);
    if (target == NULL) return;
    
    if (target->isLeaf) {
        DeleteFromLeaf(target, k);
    } else {  // 非叶结点处理
        KeyType predecessor = FindPredecessor(target, k);
        ReplaceKey(target, k, predecessor);
        target = predecessorNode;  // 转换为叶结点删除
    }
    
    // 调整平衡
    while (target->keynum < ⌈m/2-1) {
        if (canBorrowFromSibling(target)) {
            BorrowFromSibling(target);
        } else {
            MergeWithSibling(target);
            target = parent;  // 向上递归调整
        }
    }
}

易错点

  • 前驱/后继选择:非叶结点删除时未正确替换导致子树结构破坏
  • 合并顺序:合并时未正确处理父结点关键字下移导致树高降低异常

1.3 考研真题与模拟题

真题示例

题目(2022年408)
在5阶B树中插入关键字序列{15, 22, 3, 8, 71, 50},画出最终形态。

解析

  1. 初始插入15(根结点)
  2. 插入22(根结点变为[15,22])
  3. 插入3:根结点分裂为[15],左子结点[3],右子结点[22]
  4. 插入8:左子结点变为[3,8,15],分裂后根结点[8,15],子树[3]、[15,22]
  5. 插入71:右子结点[15,22,71]分裂,根结点变为[8,15,22]
  6. 插入50:右子树插入后调整,最终形态需满足5阶B树性质

模拟题

题目:给定4阶B树初始状态如下,删除关键字30后的结构如何调整?

        [20, 40]
       /   |   \
[10,15] [25,30] [45,50]

答案

  1. 删除30后,结点[25]关键字不足(1 < ⌈4/2⌉-1=1,无需调整)
  2. 父结点仍为[20,40],树结构保持平衡

二、B+树(B+Tree)

2.1 定义与性质

定义
B+树是B树的变体,满足:

  1. 结点结构
    • 非叶结点:仅存储索引关键字(子树最大值)
    • 叶结点:存储全部关键字及记录指针,并通过链表连接
  2. 子树关系:非叶结点子树数=关键字数
  3. 查找路径:所有查找均需到达叶结点

核心性质

  • 范围查询高效:叶结点链表支持顺序遍历
  • 磁盘友好:非叶结点可存储更多索引项,减少I/O次数

与B树差异

特性 B树 B+树
关键字存储 非叶结点存实际数据 非叶结点仅存索引
叶结点链接 叶结点通过指针链接
查找终止位置 可能在非叶结点 必须到达叶结点

易错点

  1. 索引理解错误:误认为非叶结点存储完整数据
  2. 范围查询实现:未利用叶结点链表特性,错误设计遍历逻辑

2.2 基本操作与实现

2.2.1 插入操作

算法步骤

  1. 查找插入位置:定位到叶结点
  2. 插入关键字:保持叶结点有序性
  3. 分裂调整:若叶结点超限,分裂为两个结点,复制最大关键字到父结点
  4. 非叶结点更新:若父结点超限,递归分裂

代码实现要点

void BPlusTreeInsert(BPlusTree *T, KeyType k) {
    LeafNode *leaf = FindLeaf(T, k);
    InsertIntoLeaf(leaf, k);
    
    if (leaf->keynum > m-1) {
        LeafNode *newLeaf = SplitLeaf(leaf);
        InsertIntoParent(leaf, newLeaf->keys[0], newLeaf);
    }
}

难点

  • 分裂策略:叶结点分裂时需维护链表指针
  • 索引更新:非叶结点分裂需复制(非移动)关键字

2.2.2 删除操作

算法步骤

  1. 查找删除位置:定位到叶结点
  2. 删除关键字:直接删除,保持有序
  3. 调整平衡
    • 借关键字:向兄弟结点借
    • 合并结点:若兄弟不足,合并叶结点并删除父结点对应关键字

实现细节

void BPlusTreeDelete(BPlusTree *T, KeyType k) {
    LeafNode *leaf = FindLeaf(T, k);
    DeleteFromLeaf(leaf, k);
    
    if (leaf->keynum < ⌈m/2) {
        if (canBorrowFromSibling(leaf)) {
            BorrowFromSibling(leaf);
        } else {
            MergeWithSibling(leaf);
            // 递归调整父结点关键字
        }
    }
}

易错点

  • 父结点关键字处理:合并叶结点后需删除父结点冗余关键字
  • 链表维护:合并时未更新叶结点链表指针

2.3 考研真题与模拟题

真题示例

题目(2023年408)
下列关于B+树的描述中,错误的是:
A. 叶结点包含全部关键字
B. 非叶结点关键字是子树的最大值
C. 支持高效的范围查询
D. 非叶结点存储实际数据记录

答案:D
解析:B+树的非叶结点仅存储索引,实际数据存储在叶结点


模拟题

题目:在3阶B+树中插入序列{5, 8, 3, 7, 1},画出最终结构。

答案

非叶结点: [5,7]
叶结点链表: [1,3] ↔ [5,7] ↔ [8]

三、综合应用与高频考点

3.1 树高与关键字数量关系

公式推导

  • 最小高度 h m i n = log ⁡ m ( N + 1 ) h_{min} = \log_m (N+1) hmin=logm(N+1)
  • 最大高度 h m a x = log ⁡ ⌈ m / 2 ⌉ N + 1 2 + 1 h_{max} = \log_{\lceil m/2 \rceil} \frac{N+1}{2} + 1 hmax=logm/22N+1+1

真题应用(2018年408)
已知某5阶B树含24个关键字,求最小高度。
解法
h m i n = log ⁡ 5 ( 24 + 1 ) = 2 h_{min} = \log_5 (24+1) = 2 hmin=log5(24+1)=2


3.2 插入删除综合题

题目:给定4阶B树初始结构如下,依次删除35、40,画出每步结果:

        [30, 40]
       /    |    \
[10,20]  [35]  [45,50]

解析

  1. 删除35:直接删除,父结点变为[30,40] → [30,40]
  2. 删除40:父结点删除40后,合并[30]与[45,50],新根为[30,45,50]

四、算法实现模板

4.1 B树查找算法

BTreeNode* BTreeSearch(BTreeNode *root, KeyType k) {
    int i = 0;
    while (i < root->n && k > root->keys[i]) i++;
    if (i < root->n && k == root->keys[i]) return root;
    if (root->isLeaf) return NULL;
    return BTreeSearch(root->children[i], k);
}

4.2 B+树范围查询

void BPlusTreeRangeQuery(BPlusTree *T, KeyType low, KeyType high) {
    LeafNode *start = FindLeaf(T, low);
    while (start != NULL && start->keys[0] <= high) {
        for (int i=0; i<start->n; i++) {
            if (start->keys[i] >= low && start->keys[i] <= high) {
                printf("%d ", start->keys[i]);
            }
        }
        start = start->next;
    }
}

五、易混淆知识点对比

对比项 B树 B+树
数据存储 非叶结点存数据 仅叶结点存数据
查询终止 可能在非叶结点找到数据 必须到达叶结点
范围查询 效率低 通过叶结点链表高效支持
分裂方式 中间关键字上移 叶结点分裂时复制关键字到父结点

六、备考建议

  1. 手绘练习:针对插入/删除操作,手动绘制每步调整过程(参考网页1、网页7)
  2. 复杂度推导:掌握树高公式的数学证明(网页2、网页6)
  3. 真题精练:重点练习2010-2023年涉及B树/B+树的真题(网页9、网页16)
  4. 代码默写:熟记插入/删除的核心代码模板(网页12、网页14)

注:建议结合《数据结构(C语言版)》与王道考研教材进行系统复习。

你可能感兴趣的:(数据结构,考研,b树)