3-13 B+树

B+树(B+ Tree)

B+树(B+ Tree)是B树(B Tree)的一种变体,由 B树发展而来,其核心区别在于:所有数据仅存储在叶子节点(Leaf Node)中,内部节点(Internal Node)仅存储路由关键字(Routing Key)用于导航。叶子节点之间通过链表(Linked List)串联,使得范围查询(Range Query)极其高效。

B+树是数据库索引和文件系统中最常用的数据结构。MySQL 的 InnoDB 引擎、PostgreSQL、NTFS 和 ext4 文件系统等都使用 B+树作为核心索引结构。

B树 vs B+树结构对比

B树(数据存储在所有节点中):

              [10=D | 20=D]
             /      |       \
       [5=D]    [15=D]   [25=D | 30=D]

B+树(数据仅在叶子节点,内部节点仅路由):

              [10 | 20]              ← 内部节点:仅存路由关键字
             /      |       \
       [5,10] -> [15,20] -> [25,30]  ← 叶子节点:存数据 + 链表串联
  • B树中每个节点都存储数据(用 =D 表示)
  • B+树内部节点只存关键字副本用于路由,所有数据集中在叶子节点
  • B+树叶子节点通过 -> 串联成链表,支持顺序遍历

与B树的区别

B+树在B树基础上做了三个关键改进,使其成为数据库索引的首选结构:

特性 B树(B Tree) B+树(B+ Tree)
数据存储位置 所有节点都存储数据 仅叶子节点存储数据
叶子节点链接 叶子之间无链接 叶子通过链表串联
关键字重复 无重复 内部节点路由关键字在叶子中有副本
范围查询 需要中序遍历整棵树 找到起点后沿链表顺序遍历
查找路径 可能在内部节点提前终止 必须走到叶子节点
单次查找 可能更快(提前命中) 稍慢(必须到叶子),但更稳定
节点扇出 较小(节点含数据占用空间大) 较大(内部节点仅存关键字)
顺序遍历 需要中序遍历所有层 直接遍历叶子链表

关键理解:

  • B+树内部节点的关键字会在叶子节点中再次出现,因为内部节点的关键字仅用于路由,不携带实际数据
  • 链表连接使范围查询从 O(n log n)(B树中序遍历)优化为 O(log n + k)(B+树沿链表遍历 k 个结果)
  • 内部节点不存数据意味着同样大小的磁盘页可以容纳更多关键字,树更矮,磁盘 I/O 更少

节点定义

B+树的节点分为两类:内部节点叶子节点,结构不同。

  • 内部节点(Internal Node):存储关键字和子节点指针,用于路由
  • 叶子节点(Leaf Node):存储关键字/数据,并通过 next 指针串联成链表

以下使用 MAX 表示每个节点最多存储的关键字数量(即阶数 Order)。

C++ 节点定义

#include <iostream>
#include <vector>
using namespace std;

const int MAX = 3;  // max keys per node

// B+ Tree node definition
struct BPlusNode {
    bool isLeaf;                    // true if this is a leaf node
    vector<int> keys;               // keys (routing keys for internal, data for leaf)

    // internal node fields
    vector<BPlusNode*> children;    // child pointers (internal nodes only)

    // leaf node fields
    BPlusNode* next;                // next leaf pointer (leaf nodes only)

    // constructor
    BPlusNode(bool leaf) : isLeaf(leaf), next(nullptr) {}
};

C++ 使用 vector 存储关键字和子节点指针,isLeaf 标志区分节点类型。内部节点使用 children,叶子节点使用 next 指向下一个叶子节点,形成链表。

C 节点定义

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX 3  // max keys per node

// B+ Tree node definition
typedef struct BPlusNode {
    int keys[MAX];                  // array of keys
    int numKeys;                    // current number of keys
    bool isLeaf;                    // true if leaf node

    // internal node fields
    struct BPlusNode* children[MAX + 1];  // child pointers

    // leaf node fields
    struct BPlusNode* next;         // next leaf in linked list
} BPlusNode;

// create a new node
BPlusNode* createNode(bool isLeaf) {
    BPlusNode* node = (BPlusNode*)malloc(sizeof(BPlusNode));
    node->isLeaf = isLeaf;
    node->numKeys = 0;
    node->next = NULL;
    for (int i = 0; i <= MAX; i++)
        node->children[i] = NULL;
    return node;
}

C 语言使用固定大小数组存储关键字(最多 MAX 个)和子节点指针(最多 MAX + 1 个),numKeys 记录当前关键字数量。叶子节点通过 next 指针串联。

Python 节点定义

MAX = 3  # max keys per node

class BPlusNode:
    def __init__(self, is_leaf=False):
        # keys stored as list
        self.keys = []
        self.is_leaf = is_leaf

        # internal node: child pointers
        self.children = []

        # leaf node: next leaf in linked list
        self.next = None

Python 使用列表存储关键字和子节点,is_leaf 区分节点类型。next 指针仅在叶子节点中使用,串联叶子链表。

Go 节点定义

package main

import "fmt"

const maxKeys = 3 // max keys per node

// BPlusNode represents a node in the B+ Tree
type BPlusNode struct {
    keys     []int        // keys (routing keys for internal, data for leaf)
    children []*BPlusNode // child pointers (internal nodes only)
    isLeaf   bool         // true if leaf node
    next     *BPlusNode   // next leaf pointer (leaf nodes only)
}

// newBPlusNode creates a new B+ Tree node
func newBPlusNode(isLeaf bool) *BPlusNode {
    return &BPlusNode{
        keys:     []int{},
        children: []*BPlusNode{},
        isLeaf:   isLeaf,
        next:     nil,
    }
}

Go 使用切片存储关键字和子节点指针,isLeaf 标志区分节点类型。内部节点使用 children,叶子节点使用 next 指向下一个叶子节点,形成链表。


搜索操作

B+树的搜索(Search)与B树有一个重要区别:必须到达叶子节点才能确认是否找到目标,因为内部节点只存路由关键字,不存实际数据。

搜索步骤:

  1. 从根节点开始,在当前节点的关键字中找到合适的区间
  2. 沿对应的子节点指针向下搜索
  3. 到达叶子节点后,在叶子中查找目标关键字
搜索 15 的过程:

              [10 | 20]         ← 10 < 15 < 20, 进入子节点 c[1]
             /      |       \
       [5,10] -> [15,20] -> [25,30]   ← 在叶子 [15,20] 中找到 15, 搜索成功

C++ 搜索实现

// search for key k in B+ tree rooted at node
BPlusNode* search(BPlusNode* node, int k, int* index) {
    if (node == nullptr) return nullptr;

    int i = 0;
    // find first key > k (for internal nodes, go left if equal)
    while (i < (int)node->keys.size() && k >= node->keys[i])
        i++;

    if (node->isLeaf) {
        // in leaf: check if previous key matches
        if (i > 0 && node->keys[i - 1] == k) {
            *index = i - 1;
            return node;
        }
        return nullptr;
    }

    // recurse into appropriate child
    return search(node->children[i], k, index);
}

C 搜索实现

// search for key k in B+ tree rooted at node
BPlusNode* search(BPlusNode* node, int k, int* index) {
    if (node == NULL) return NULL;

    int i = 0;
    // find first key > k
    while (i < node->numKeys && k >= node->keys[i])
        i++;

    if (node->isLeaf) {
        // in leaf: check if previous key matches
        if (i > 0 && node->keys[i - 1] == k) {
            *index = i - 1;
            return node;
        }
        return NULL;
    }

    // recurse into appropriate child
    return search(node->children[i], k, index);
}

Python 搜索实现

def search(node, k):
    """Search for key k in B+ tree rooted at node."""
    if node is None:
        return None, -1

    i = 0
    # find first key > k
    while i < len(node.keys) and k >= node.keys[i]:
        i += 1

    if node.is_leaf:
        # in leaf: check if previous key matches
        if i > 0 and node.keys[i - 1] == k:
            return node, i - 1
        return None, -1

    # recurse into appropriate child
    return search(node.children[i], k)

Go 搜索实现

// search searches for key k in B+ tree rooted at node
func search(node *BPlusNode, k int) (*BPlusNode, int) {
    if node == nil {
        return nil, -1
    }

    i := 0
    // find first key > k
    for i < len(node.keys) && k >= node.keys[i] {
        i++
    }

    if node.isLeaf {
        // in leaf: check if previous key matches
        if i > 0 && node.keys[i-1] == k {
            return node, i - 1
        }
        return nil, -1
    }

    // recurse into appropriate child
    return search(node.children[i], k)
}

B+树的搜索必须走到叶子节点,这使得每次搜索的路径长度固定为树高,性能更稳定。搜索的时间复杂度为 O(log n),与B树相同。


范围查询

范围查询(Range Query)是B+树的核心优势。在B树中,范围查询需要中序遍历(Inorder Traversal)整棵树,时间复杂度为 O(n log n);而在B+树中,只需先定位到起始叶子节点,然后沿链表顺序遍历即可,时间复杂度为 O(log n + k),其中 k 是结果数量。

范围查询 [15, 35] 的过程:

1. 搜索 15 → 到达叶子节点 [15, 20]
2. 从 [15, 20] 开始沿链表遍历:
   [15, 20] -> [25, 30] -> [35, 40]
3. 收集 [15, 20] 中的 15, 20
4. 收集 [25, 30] 中的 25, 30
5. 收集 [35, 40] 中的 35
6. 到达 40 > 35,停止
结果: 15, 20, 25, 30, 35

C++ 范围查询实现

// range query: find all keys in [lo, hi]
vector<int> rangeQuery(BPlusNode* root, int lo, int hi) {
    vector<int> result;
    if (root == nullptr) return result;

    // find the leaf node containing lo
    BPlusNode* node = root;
    while (!node->isLeaf) {
        int i = 0;
        while (i < (int)node->keys.size() && lo >= node->keys[i])
            i++;
        node = node->children[i];
    }

    // traverse leaf linked list from starting leaf
    while (node != nullptr) {
        for (int k : node->keys) {
            if (k > hi) return result;   // past upper bound
            if (k >= lo) result.push_back(k);
        }
        node = node->next;  // follow linked list
    }
    return result;
}

C 范围查询实现

// range query: print all keys in [lo, hi]
void rangeQuery(BPlusNode* root, int lo, int hi) {
    if (root == NULL) return;

    // find the leaf node containing lo
    BPlusNode* node = root;
    while (!node->isLeaf) {
        int i = 0;
        while (i < node->numKeys && lo >= node->keys[i])
            i++;
        node = node->children[i];
    }

    // traverse leaf linked list from starting leaf
    int first = 1;
    while (node != NULL) {
        for (int j = 0; j < node->numKeys; j++) {
            if (node->keys[j] > hi) return;  // past upper bound
            if (node->keys[j] >= lo) {
                if (!first) printf(", ");
                printf("%d", node->keys[j]);
                first = 0;
            }
        }
        node = node->next;  // follow linked list
    }
}

Python 范围查询实现

def range_query(root, lo, hi):
    """Range query: find all keys in [lo, hi]."""
    result = []
    if root is None:
        return result

    # find the leaf node containing lo
    node = root
    while not node.is_leaf:
        i = 0
        while i < len(node.keys) and lo >= node.keys[i]:
            i += 1
        node = node.children[i]

    # traverse leaf linked list from starting leaf
    while node is not None:
        for k in node.keys:
            if k > hi:
                return result   # past upper bound
            if k >= lo:
                result.append(k)
        node = node.next  # follow linked list
    return result

Go 范围查询实现

// rangeQuery finds all keys in [lo, hi]
func rangeQuery(root *BPlusNode, lo, hi int) []int {
    result := []int{}
    if root == nil {
        return result
    }

    // find the leaf node containing lo
    node := root
    for !node.isLeaf {
        i := 0
        for i < len(node.keys) && lo >= node.keys[i] {
            i++
        }
        node = node.children[i]
    }

    // traverse leaf linked list from starting leaf
    for node != nil {
        for _, k := range node.keys {
            if k > hi {
                return result // past upper bound
            }
            if k >= lo {
                result = append(result, k)
            }
        }
        node = node.next // follow linked list
    }
    return result
}

范围查询的关键步骤:先通过 O(log n) 的搜索定位起始叶子节点,然后沿链表顺序遍历收集结果。这比B树的中序遍历高效得多,尤其在结果集较大时。


插入操作

B+树的插入(Insert)过程与B树类似,但需要额外维护叶子链表。具体步骤如下:

  1. 找到目标叶子节点,将关键字插入到正确位置
  2. 如果叶子节点未满(关键字数 <= MAX),直接插入完成
  3. 如果叶子节点已满,分裂(Split)为两个叶子节点,将中间关键字提升到父节点
  4. 更新叶子链表的连接
  5. 如果父节点也满了,继续向上分裂,可能需要创建新的根节点
插入序列: 10, 20, 5, 15, 25, 30, 35, 40  (MAX = 3)

插入 10:  叶子 [10]
插入 20:  叶子 [10, 20]
插入 5:   叶子 [5, 10, 20]
插入 15:  叶子满,分裂
          [10]              ← 新内部节点
         /    \
      [5,10] -> [15,20]     ← 叶子链表
插入 25:  [5,10] -> [15,20,25]
插入 30:  [5,10] -> [15,20,25,30]  满,分裂
          [10 | 20]         ← 内部节点
         /    |     \
      [5,10] -> [15,20] -> [25,30]
插入 35:  [5,10] -> [15,20] -> [25,30,35]
插入 40:  [5,10] -> [15,20] -> [25,30,35,40]  满,分裂
          [10 | 20 | 30]    ← 内部节点满,分裂
              [20]          ← 新根
             /    \
          [10]   [30]
         /   \    |   \
      [5,10]->[15,20]->[25,30]->[35,40]

C++ 插入实现

// find the leaf node where key k should be inserted
BPlusNode* findLeaf(BPlusNode* root, int k) {
    BPlusNode* node = root;
    while (!node->isLeaf) {
        int i = 0;
        while (i < (int)node->keys.size() && k >= node->keys[i])
            i++;
        node = node->children[i];
    }
    return node;
}

// insert key into leaf, keeping sorted order
void insertIntoLeaf(BPlusNode* leaf, int k) {
    int i = 0;
    while (i < (int)leaf->keys.size() && leaf->keys[i] < k)
        i++;
    leaf->keys.insert(leaf->keys.begin() + i, k);
}

// split a full internal node, return new node and promoted key
struct SplitResult {
    BPlusNode* newNode;
    int promotedKey;
};

SplitResult splitInternal(BPlusNode* node) {
    int mid = node->keys.size() / 2;
    int promoted = node->keys[mid];

    BPlusNode* newNode = new BPlusNode(false);
    // right half goes to new node
    newNode->keys.assign(node->keys.begin() + mid + 1, node->keys.end());
    newNode->children.assign(node->children.begin() + mid + 1, node->children.end());

    // trim original node to left half
    node->keys.resize(mid);
    node->children.resize(mid + 1);

    return {newNode, promoted};
}

// insert key into B+ tree, return new root if tree grew
BPlusNode* insert(BPlusNode* root, int k) {
    if (root == nullptr) {
        root = new BPlusNode(true);
        root->keys.push_back(k);
        return root;
    }

    // find target leaf
    BPlusNode* leaf = findLeaf(root, k);
    insertIntoLeaf(leaf, k);

    // if leaf overflows, split and propagate up
    if ((int)leaf->keys.size() > MAX) {
        int mid = leaf->keys.size() / 2;

        BPlusNode* newLeaf = new BPlusNode(true);
        // right half goes to new leaf
        newLeaf->keys.assign(leaf->keys.begin() + mid, leaf->keys.end());
        // trim original leaf to left half
        leaf->keys.resize(mid);

        // update linked list: leaf -> newLeaf -> old next
        newLeaf->next = leaf->next;
        leaf->next = newLeaf;

        int promoted = newLeaf->keys[0];  // first key of new leaf

        // propagate split upward
        BPlusNode* current = leaf;
        BPlusNode* sibling = newLeaf;
        int keyToInsert = promoted;

        // walk up using parent pointers (simplified: rebuild path)
        // For this example, we use a bottom-up approach
        // In practice, maintain parent pointers or rebuild path
        // Here we show the recursive insert approach instead
    }

    return root;
}

C 插入实现

// find the leaf node where key k should be inserted
BPlusNode* findLeaf(BPlusNode* root, int k) {
    BPlusNode* node = root;
    while (!node->isLeaf) {
        int i = 0;
        while (i < node->numKeys && k >= node->keys[i])
            i++;
        node = node->children[i];
    }
    return node;
}

// insert key into leaf in sorted order
void insertIntoLeaf(BPlusNode* leaf, int k) {
    int i = leaf->numKeys - 1;
    while (i >= 0 && leaf->keys[i] > k) {
        leaf->keys[i + 1] = leaf->keys[i];
        i--;
    }
    leaf->keys[i + 1] = k;
    leaf->numKeys++;
}

// split a full leaf node, return new leaf
BPlusNode* splitLeaf(BPlusNode* leaf) {
    BPlusNode* newLeaf = createNode(true);
    int mid = (leaf->numKeys + 1) / 2;

    // copy right half to new leaf
    newLeaf->numKeys = leaf->numKeys - mid;
    for (int i = 0; i < newLeaf->numKeys; i++)
        newLeaf->keys[i] = leaf->keys[i + mid];

    // update linked list
    newLeaf->next = leaf->next;
    leaf->next = newLeaf;

    // trim original leaf
    leaf->numKeys = mid;
    return newLeaf;
}

Python 插入实现

def find_leaf(root, k):
    """Find the leaf node where key k should be inserted."""
    node = root
    while not node.is_leaf:
        i = 0
        while i < len(node.keys) and k >= node.keys[i]:
            i += 1
        node = node.children[i]
    return node


def insert_into_leaf(leaf, k):
    """Insert key into leaf maintaining sorted order."""
    i = 0
    while i < len(leaf.keys) and leaf.keys[i] < k:
        i += 1
    leaf.keys.insert(i, k)


def split_leaf(leaf):
    """Split a full leaf node, return (new_leaf, promoted_key)."""
    mid = len(leaf.keys) // 2
    new_leaf = BPlusNode(is_leaf=True)

    # right half goes to new leaf
    new_leaf.keys = leaf.keys[mid:]
    promoted = new_leaf.keys[0]

    # trim original leaf to left half
    leaf.keys = leaf.keys[:mid]

    # update linked list: leaf -> new_leaf -> old next
    new_leaf.next = leaf.next
    leaf.next = new_leaf

    return new_leaf, promoted

Go 插入实现

// findLeaf finds the leaf node where key k should be inserted
func findLeaf(root *BPlusNode, k int) *BPlusNode {
    node := root
    for !node.isLeaf {
        i := 0
        for i < len(node.keys) && k >= node.keys[i] {
            i++
        }
        node = node.children[i]
    }
    return node
}

// insertIntoLeaf inserts key into leaf maintaining sorted order
func insertIntoLeaf(leaf *BPlusNode, k int) {
    i := 0
    for i < len(leaf.keys) && leaf.keys[i] < k {
        i++
    }
    // insert at position i
    leaf.keys = append(leaf.keys, 0)
    copy(leaf.keys[i+1:], leaf.keys[i:])
    leaf.keys[i] = k
}

// splitLeaf splits a full leaf node, returns (new_leaf, promoted_key)
func splitLeaf(leaf *BPlusNode) (*BPlusNode, int) {
    mid := len(leaf.keys) / 2
    newLeaf := newBPlusNode(true)

    // right half goes to new leaf
    newLeaf.keys = append(newLeaf.keys, leaf.keys[mid:]...)
    promoted := newLeaf.keys[0]

    // trim original leaf to left half
    leaf.keys = leaf.keys[:mid]

    // update linked list: leaf -> newLeaf -> old next
    newLeaf.next = leaf.next
    leaf.next = newLeaf

    return newLeaf, promoted
}

插入操作的核心是分裂传播:当一个节点分裂时,中间关键字被提升到父节点;如果父节点也满了,继续向上分裂。叶子节点分裂时还需要更新链表指针。最坏情况下分裂传播到根节点,树高增加一层。


遍历操作

B+树的遍历(Traversal)极为简单:从最左侧的叶子节点开始,沿链表依次访问所有叶子节点即可获得全部关键字的升序排列。这比B树的中序遍历(递归访问子树-关键字-子树)简单得多。

遍历过程:

              [10 | 20]
             /      |       \
       [5,10] -> [15,20] -> [25,30]

从最左侧叶子 [5,10] 开始:
[5,10] -> [15,20] -> [25,30]
输出: 5 10 15 20 25 30

C++ 遍历实现

// traverse all keys via leaf linked list (sorted order)
void traverse(BPlusNode* root) {
    if (root == nullptr) return;

    // find leftmost leaf
    BPlusNode* node = root;
    while (!node->isLeaf)
        node = node->children[0];

    // walk the linked list
    while (node != nullptr) {
        for (int k : node->keys)
            cout << k << " ";
        node = node->next;
    }
}

// print tree structure level by level
void printTree(BPlusNode* node, int level) {
    if (node == nullptr) return;

    cout << "Level " << level << ": [";
    for (int i = 0; i < (int)node->keys.size(); i++) {
        if (i > 0) cout << " | ";
        cout << node->keys[i];
    }
    cout << "]" << (node->isLeaf ? " (leaf)" : "") << endl;

    if (!node->isLeaf) {
        for (auto child : node->children)
            printTree(child, level + 1);
    }
}

C 遍历实现

// traverse all keys via leaf linked list (sorted order)
void traverse(BPlusNode* root) {
    if (root == NULL) return;

    // find leftmost leaf
    BPlusNode* node = root;
    while (!node->isLeaf)
        node = node->children[0];

    // walk the linked list
    int first = 1;
    while (node != NULL) {
        for (int i = 0; i < node->numKeys; i++) {
            if (!first) printf(" ");
            printf("%d", node->keys[i]);
            first = 0;
        }
        node = node->next;
    }
    printf("\n");
}

// print leaf nodes with links
void printLeaves(BPlusNode* root) {
    if (root == NULL) return;

    // find leftmost leaf
    BPlusNode* node = root;
    while (!node->isLeaf)
        node = node->children[0];

    // print each leaf with link arrows
    while (node != NULL) {
        printf("[");
        for (int i = 0; i < node->numKeys; i++) {
            if (i > 0) printf(",");
            printf("%d", node->keys[i]);
        }
        printf("]");
        if (node->next != NULL) printf(" -> ");
        node = node->next;
    }
    printf("\n");
}

Python 遍历实现

def traverse(root):
    """Traverse all keys via leaf linked list (sorted order)."""
    if root is None:
        return

    # find leftmost leaf
    node = root
    while not node.is_leaf:
        node = node.children[0]

    # walk the linked list
    result = []
    while node is not None:
        for k in node.keys:
            result.append(k)
        node = node.next
    return result


def print_leaves(root):
    """Print leaf nodes with link arrows."""
    if root is None:
        return

    # find leftmost leaf
    node = root
    while not node.is_leaf:
        node = node.children[0]

    # print each leaf with link arrows
    parts = []
    while node is not None:
        parts.append(str(node.keys))
        node = node.next
    print(" -> ".join(parts))

Go 遍历实现

// traverse walks all keys via leaf linked list (sorted order)
func traverse(root *BPlusNode) []int {
    if root == nil {
        return nil
    }

    // find leftmost leaf
    node := root
    for !node.isLeaf {
        node = node.children[0]
    }

    // walk the linked list
    result := []int{}
    for node != nil {
        result = append(result, node.keys...)
        node = node.next
    }
    return result
}

// printLeaves prints leaf nodes with link arrows
func printLeaves(root *BPlusNode) {
    if root == nil {
        return
    }

    // find leftmost leaf
    node := root
    for !node.isLeaf {
        node = node.children[0]
    }

    // print each leaf with link arrows
    first := true
    for node != nil {
        if !first {
            fmt.Print(" -> ")
        }
        first = false
        fmt.Print("[")
        for i, k := range node.keys {
            if i > 0 {
                fmt.Print(",")
            }
            fmt.Print(k)
        }
        fmt.Print("]")
        node = node.next
    }
    fmt.Println()
}

遍历操作直接沿叶子链表前进,无需递归,时间复杂度为 O(n)。这种设计使得顺序读取全部数据非常高效,特别适合数据库的全表扫描(Full Table Scan)和索引扫描(Index Scan)。


完整实现

以下提供 C++、C、Python 三种语言的 B+树完整实现,包含搜索、范围查询、插入和遍历功能。为了代码的可读性和教学目的,实现中使用简化的递归插入方式。

C++ 完整实现

#include <iostream>
#include <vector>
using namespace std;

const int MAX = 3;  // max keys per node

struct BPlusNode {
    bool isLeaf;
    vector<int> keys;
    vector<BPlusNode*> children;
    BPlusNode* next;

    BPlusNode(bool leaf) : isLeaf(leaf), next(nullptr) {}
};

// find the leaf where key k resides
BPlusNode* findLeaf(BPlusNode* root, int k) {
    BPlusNode* node = root;
    while (!node->isLeaf) {
        int i = 0;
        while (i < (int)node->keys.size() && k >= node->keys[i])
            i++;
        node = node->children[i];
    }
    return node;
}

// search for key k
bool search(BPlusNode* root, int k) {
    if (root == nullptr) return false;
    BPlusNode* leaf = findLeaf(root, k);
    for (int key : leaf->keys)
        if (key == k) return true;
    return false;
}

// insert key into B+ tree, return (new_root, new_sibling_if_split)
pair<BPlusNode*, BPlusNode*> insertInternal(BPlusNode* node, int k);

// insert into leaf node, handling overflow
pair<BPlusNode*, BPlusNode*> insertLeaf(BPlusNode* leaf, int k) {
    // insert key in sorted order
    int i = 0;
    while (i < (int)leaf->keys.size() && leaf->keys[i] < k)
        i++;
    leaf->keys.insert(leaf->keys.begin() + i, k);

    // if no overflow, done
    if ((int)leaf->keys.size() <= MAX) {
        return {nullptr, nullptr};
    }

    // split leaf: left half stays, right half goes to new leaf
    int mid = leaf->keys.size() / 2;
    BPlusNode* newLeaf = new BPlusNode(true);
    newLeaf->keys.assign(leaf->keys.begin() + mid, leaf->keys.end());
    leaf->keys.resize(mid);

    // maintain linked list
    newLeaf->next = leaf->next;
    leaf->next = newLeaf;

    return {nullptr, newLeaf};
}

// insert into internal node, handling overflow
pair<BPlusNode*, BPlusNode*> insertInternal(BPlusNode* node, int k) {
    // find child to descend into
    int i = 0;
    while (i < (int)node->keys.size() && k >= node->keys[i])
        i++;

    BPlusNode* child = node->children[i];
    pair<BPlusNode*, BPlusNode*> result;

    // recurse: insert into child
    if (child->isLeaf)
        result = insertLeaf(child, k);
    else
        result = insertInternal(child, k);

    BPlusNode* splitNode = result.second;
    if (splitNode == nullptr) {
        return {nullptr, nullptr};  // no split occurred
    }

    // child split: insert promoted key and new child into this node
    int promotedKey = splitNode->keys[0];
    node->keys.insert(node->keys.begin() + i, promotedKey);
    node->children.insert(node->children.begin() + i + 1, splitNode);

    // if no overflow, done
    if ((int)node->keys.size() <= MAX) {
        return {nullptr, nullptr};
    }

    // split internal node
    int mid = node->keys.size() / 2;
    int promotedUp = node->keys[mid];

    BPlusNode* newNode = new BPlusNode(false);
    newNode->keys.assign(node->keys.begin() + mid + 1, node->keys.end());
    newNode->children.assign(node->children.begin() + mid + 1, node->children.end());

    node->keys.resize(mid);
    node->children.resize(mid + 1);

    // return new internal node; use keys[0] trick for promoted key
    newNode->keys.insert(newNode->keys.begin(), promotedUp);
    return {nullptr, newNode};
}

// main insert function
BPlusNode* insert(BPlusNode* root, int k) {
    if (root == nullptr) {
        root = new BPlusNode(true);
        root->keys.push_back(k);
        return root;
    }

    // try inserting into tree
    pair<BPlusNode*, BPlusNode*> result;
    if (root->isLeaf)
        result = insertLeaf(root, k);
    else
        result = insertInternal(root, k);

    // if root split, create new root
    if (result.second != nullptr) {
        BPlusNode* newRoot = new BPlusNode(false);
        newRoot->keys.push_back(result.second->keys[0]);
        newRoot->children.push_back(root);
        newRoot->children.push_back(result.second);
        root = newRoot;
    }

    return root;
}

// traverse all keys via leaf linked list
void traverse(BPlusNode* root) {
    if (root == nullptr) return;

    BPlusNode* node = root;
    while (!node->isLeaf)
        node = node->children[0];

    bool first = true;
    while (node != nullptr) {
        for (int k : node->keys) {
            if (!first) cout << " ";
            cout << k;
            first = false;
        }
        node = node->next;
    }
    cout << endl;
}

// print leaf nodes with links
void printLeaves(BPlusNode* root) {
    if (root == nullptr) return;

    BPlusNode* node = root;
    while (!node->isLeaf)
        node = node->children[0];

    while (node != nullptr) {
        cout << "[";
        for (int i = 0; i < (int)node->keys.size(); i++) {
            if (i > 0) cout << ",";
            cout << node->keys[i];
        }
        cout << "]";
        if (node->next != nullptr) cout << " -> ";
        node = node->next;
    }
    cout << endl;
}

// range query: find all keys in [lo, hi]
void rangeQuery(BPlusNode* root, int lo, int hi) {
    if (root == nullptr) return;

    BPlusNode* node = findLeaf(root, lo);
    bool first = true;
    while (node != nullptr) {
        for (int k : node->keys) {
            if (k > hi) {
                cout << endl;
                return;
            }
            if (k >= lo) {
                if (!first) cout << " ";
                cout << k;
                first = false;
            }
        }
        node = node->next;
    }
    cout << endl;
}

int main() {
    BPlusNode* root = nullptr;

    // insert sequence
    int keys[] = {10, 20, 5, 15, 25, 30, 35, 40};
    cout << "Insert sequence: ";
    for (int k : keys) {
        cout << k << " ";
        root = insert(root, k);
    }
    cout << endl << endl;

    // show leaf structure
    cout << "Leaf nodes (linked list): ";
    printLeaves(root);

    // traverse all keys
    cout << "Traversal: ";
    traverse(root);

    // search
    cout << "Search 15: " << (search(root, 15) ? "Found" : "Not found") << endl;
    cout << "Search 12: " << (search(root, 12) ? "Found" : "Not found") << endl;

    // range query
    cout << "Range query [15, 35]: ";
    rangeQuery(root, 15, 35);

    return 0;
}

C 完整实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX 3

typedef struct BPlusNode {
    int keys[MAX + 1];          // extra slot for temporary overflow
    int numKeys;
    bool isLeaf;
    struct BPlusNode* children[MAX + 2];  // extra slot for overflow
    struct BPlusNode* next;     // leaf linked list
} BPlusNode;

BPlusNode* createNode(bool isLeaf) {
    BPlusNode* node = (BPlusNode*)malloc(sizeof(BPlusNode));
    node->isLeaf = isLeaf;
    node->numKeys = 0;
    node->next = NULL;
    for (int i = 0; i <= MAX + 1; i++)
        node->children[i] = NULL;
    return node;
}

// find the leaf where key k resides
BPlusNode* findLeaf(BPlusNode* root, int k) {
    BPlusNode* node = root;
    while (!node->isLeaf) {
        int i = 0;
        while (i < node->numKeys && k >= node->keys[i])
            i++;
        node = node->children[i];
    }
    return node;
}

// search for key k
bool search(BPlusNode* root, int k) {
    if (root == NULL) return false;
    BPlusNode* leaf = findLeaf(root, k);
    for (int i = 0; i < leaf->numKeys; i++)
        if (leaf->keys[i] == k) return true;
    return false;
}

// split result: promoted key + new sibling node
typedef struct {
    int promotedKey;
    BPlusNode* sibling;
} SplitResult;

// forward declaration
SplitResult insertIntoNode(BPlusNode* node, int k);

// insert into leaf, return split result
SplitResult insertIntoLeaf(BPlusNode* leaf, int k) {
    // insert key in sorted order
    int i = leaf->numKeys - 1;
    while (i >= 0 && leaf->keys[i] > k) {
        leaf->keys[i + 1] = leaf->keys[i];
        i--;
    }
    leaf->keys[i + 1] = k;
    leaf->numKeys++;

    SplitResult sr = {0, NULL};

    // if no overflow, done
    if (leaf->numKeys <= MAX) return sr;

    // split leaf
    int mid = leaf->numKeys / 2;
    BPlusNode* newLeaf = createNode(true);
    newLeaf->numKeys = leaf->numKeys - mid;
    for (int j = 0; j < newLeaf->numKeys; j++)
        newLeaf->keys[j] = leaf->keys[j + mid];

    // maintain linked list
    newLeaf->next = leaf->next;
    leaf->next = newLeaf;
    leaf->numKeys = mid;

    sr.promotedKey = newLeaf->keys[0];
    sr.sibling = newLeaf;
    return sr;
}

// insert into internal node, return split result
SplitResult insertIntoNode(BPlusNode* node, int k) {
    // find child to descend into
    int i = 0;
    while (i < node->numKeys && k >= node->keys[i])
        i++;

    BPlusNode* child = node->children[i];
    SplitResult sr;
    if (child->isLeaf)
        sr = insertIntoLeaf(child, k);
    else
        sr = insertIntoNode(child, k);

    if (sr.sibling == NULL) {
        // no split
        SplitResult empty = {0, NULL};
        return empty;
    }

    // child split: insert promoted key and sibling into this node
    int j = node->numKeys - 1;
    while (j >= i) {
        node->keys[j + 1] = node->keys[j];
        node->children[j + 2] = node->children[j + 1];
        j--;
    }
    node->keys[i] = sr.promotedKey;
    node->children[i + 1] = sr.sibling;
    node->numKeys++;

    SplitResult result = {0, NULL};

    // if no overflow, done
    if (node->numKeys <= MAX) return result;

    // split internal node
    int mid = node->numKeys / 2;
    BPlusNode* newNode = createNode(false);
    newNode->numKeys = node->numKeys - mid - 1;
    for (int j = 0; j < newNode->numKeys; j++)
        newNode->keys[j] = node->keys[j + mid + 1];
    for (int j = 0; j <= newNode->numKeys; j++)
        newNode->children[j] = node->children[j + mid + 1];

    result.promotedKey = node->keys[mid];
    result.sibling = newNode;
    node->numKeys = mid;
    return result;
}

// main insert function
BPlusNode* insert(BPlusNode* root, int k) {
    if (root == NULL) {
        root = createNode(true);
        root->keys[0] = k;
        root->numKeys = 1;
        return root;
    }

    SplitResult sr;
    if (root->isLeaf)
        sr = insertIntoLeaf(root, k);
    else
        sr = insertIntoNode(root, k);

    // if root split, create new root
    if (sr.sibling != NULL) {
        BPlusNode* newRoot = createNode(false);
        newRoot->keys[0] = sr.promotedKey;
        newRoot->children[0] = root;
        newRoot->children[1] = sr.sibling;
        newRoot->numKeys = 1;
        return newRoot;
    }

    return root;
}

// traverse via leaf linked list
void traverse(BPlusNode* root) {
    if (root == NULL) return;

    BPlusNode* node = root;
    while (!node->isLeaf)
        node = node->children[0];

    int first = 1;
    while (node != NULL) {
        for (int i = 0; i < node->numKeys; i++) {
            if (!first) printf(" ");
            printf("%d", node->keys[i]);
            first = 0;
        }
        node = node->next;
    }
    printf("\n");
}

// print leaf nodes with links
void printLeaves(BPlusNode* root) {
    if (root == NULL) return;

    BPlusNode* node = root;
    while (!node->isLeaf)
        node = node->children[0];

    while (node != NULL) {
        printf("[");
        for (int i = 0; i < node->numKeys; i++) {
            if (i > 0) printf(",");
            printf("%d", node->keys[i]);
        }
        printf("]");
        if (node->next != NULL) printf(" -> ");
        node = node->next;
    }
    printf("\n");
}

// range query: print all keys in [lo, hi]
void rangeQuery(BPlusNode* root, int lo, int hi) {
    if (root == NULL) return;

    BPlusNode* node = findLeaf(root, lo);
    int first = 1;
    while (node != NULL) {
        for (int i = 0; i < node->numKeys; i++) {
            if (node->keys[i] > hi) {
                printf("\n");
                return;
            }
            if (node->keys[i] >= lo) {
                if (!first) printf(" ");
                printf("%d", node->keys[i]);
                first = 0;
            }
        }
        node = node->next;
    }
    printf("\n");
}

int main() {
    BPlusNode* root = NULL;

    printf("Insert sequence: ");
    int keys[] = {10, 20, 5, 15, 25, 30, 35, 40};
    int n = sizeof(keys) / sizeof(keys[0]);
    for (int i = 0; i < n; i++) {
        printf("%d ", keys[i]);
        root = insert(root, keys[i]);
    }
    printf("\n\n");

    printf("Leaf nodes (linked list): ");
    printLeaves(root);

    printf("Traversal: ");
    traverse(root);

    printf("Search 15: %s\n", search(root, 15) ? "Found" : "Not found");
    printf("Search 12: %s\n", search(root, 12) ? "Found" : "Not found");

    printf("Range query [15, 35]: ");
    rangeQuery(root, 15, 35);

    return 0;
}

Python 完整实现

MAX = 3  # max keys per node

class BPlusNode:
    def __init__(self, is_leaf=False):
        self.keys = []
        self.children = []
        self.is_leaf = is_leaf
        self.next = None  # leaf linked list


def find_leaf(root, k):
    """Find the leaf node where key k should reside."""
    node = root
    while not node.is_leaf:
        i = 0
        while i < len(node.keys) and k >= node.keys[i]:
            i += 1
        node = node.children[i]
    return node


def search(root, k):
    """Search for key k in B+ tree."""
    if root is None:
        return False
    leaf = find_leaf(root, k)
    return k in leaf.keys


def _insert_leaf(leaf, k):
    """Insert key into leaf node. Return (promoted_key, new_sibling) or (None, None)."""
    # insert in sorted order
    i = 0
    while i < len(leaf.keys) and leaf.keys[i] < k:
        i += 1
    leaf.keys.insert(i, k)

    # no overflow
    if len(leaf.keys) <= MAX:
        return None, None

    # split leaf
    mid = len(leaf.keys) // 2
    new_leaf = BPlusNode(is_leaf=True)
    new_leaf.keys = leaf.keys[mid:]
    leaf.keys = leaf.keys[:mid]

    # maintain linked list
    new_leaf.next = leaf.next
    leaf.next = new_leaf

    return new_leaf.keys[0], new_leaf


def _insert_internal(node, k):
    """Insert key into internal node. Return (promoted_key, new_sibling) or (None, None)."""
    # find child to descend into
    i = 0
    while i < len(node.keys) and k >= node.keys[i]:
        i += 1

    child = node.children[i]
    if child.is_leaf:
        promoted, sibling = _insert_leaf(child, k)
    else:
        promoted, sibling = _insert_internal(child, k)

    if sibling is None:
        return None, None  # no split

    # child split: insert promoted key and sibling into this node
    node.keys.insert(i, promoted)
    node.children.insert(i + 1, sibling)

    # no overflow
    if len(node.keys) <= MAX:
        return None, None

    # split internal node
    mid = len(node.keys) // 2
    promoted_up = node.keys[mid]

    new_node = BPlusNode(is_leaf=False)
    new_node.keys = node.keys[mid + 1:]
    new_node.children = node.children[mid + 1:]

    node.keys = node.keys[:mid]
    node.children = node.children[:mid + 1]

    return promoted_up, new_node


def insert(root, k):
    """Insert key k into B+ tree, return new root."""
    if root is None:
        root = BPlusNode(is_leaf=True)
        root.keys.append(k)
        return root

    # insert and check for root split
    if root.is_leaf:
        promoted, sibling = _insert_leaf(root, k)
    else:
        promoted, sibling = _insert_internal(root, k)

    if sibling is not None:
        # root split: create new root
        new_root = BPlusNode(is_leaf=False)
        new_root.keys = [promoted]
        new_root.children = [root, sibling]
        return new_root

    return root


def traverse(root):
    """Traverse all keys via leaf linked list."""
    if root is None:
        return []

    node = root
    while not node.is_leaf:
        node = node.children[0]

    result = []
    while node is not None:
        result.extend(node.keys)
        node = node.next
    return result


def print_leaves(root):
    """Print leaf nodes with link arrows."""
    if root is None:
        return

    node = root
    while not node.is_leaf:
        node = node.children[0]

    parts = []
    while node is not None:
        parts.append(str(node.keys))
        node = node.next
    print(" -> ".join(parts))


def range_query(root, lo, hi):
    """Range query: find all keys in [lo, hi]."""
    if root is None:
        return []

    node = find_leaf(root, lo)
    result = []
    while node is not None:
        for k in node.keys:
            if k > hi:
                return result
            if k >= lo:
                result.append(k)
        node = node.next
    return result


if __name__ == '__main__':
    root = None

    # insert sequence
    keys = [10, 20, 5, 15, 25, 30, 35, 40]
    print("Insert sequence:", " ".join(str(k) for k in keys))
    for k in keys:
        root = insert(root, k)
    print()

    # show leaf structure
    print("Leaf nodes (linked list): ", end="")
    print_leaves(root)

    # traverse all keys
    print("Traversal:", " ".join(str(k) for k in traverse(root)))

    # search
    print(f"Search 15: {'Found' if search(root, 15) else 'Not found'}")
    print(f"Search 12: {'Found' if search(root, 12) else 'Not found'}")

    # range query
    result = range_query(root, 15, 35)
    print("Range query [15, 35]:", " ".join(str(k) for k in result))

Go 完整实现

package main

import "fmt"

const maxKeys = 3 // max keys per node

// BPlusNode represents a node in the B+ Tree
type BPlusNode struct {
    keys     []int
    children []*BPlusNode
    isLeaf   bool
    next     *BPlusNode
}

func newBPlusNode(isLeaf bool) *BPlusNode {
    return &BPlusNode{
        keys:     []int{},
        children: []*BPlusNode{},
        isLeaf:   isLeaf,
        next:     nil,
    }
}

// findLeaf finds the leaf node where key k should reside
func findLeaf(root *BPlusNode, k int) *BPlusNode {
    node := root
    for !node.isLeaf {
        i := 0
        for i < len(node.keys) && k >= node.keys[i] {
            i++
        }
        node = node.children[i]
    }
    return node
}

// bpSearch searches for key k in B+ tree
func bpSearch(root *BPlusNode, k int) bool {
    if root == nil {
        return false
    }
    leaf := findLeaf(root, k)
    for _, key := range leaf.keys {
        if key == k {
            return true
        }
    }
    return false
}

// insertLeaf inserts key into leaf node, returns (promoted_key, new_sibling)
func insertLeaf(leaf *BPlusNode, k int) (int, *BPlusNode) {
    // insert in sorted order
    i := 0
    for i < len(leaf.keys) && leaf.keys[i] < k {
        i++
    }
    leaf.keys = append(leaf.keys, 0)
    copy(leaf.keys[i+1:], leaf.keys[i:])
    leaf.keys[i] = k

    // no overflow
    if len(leaf.keys) <= maxKeys {
        return 0, nil
    }

    // split leaf
    mid := len(leaf.keys) / 2
    newLeaf := newBPlusNode(true)
    newLeaf.keys = append(newLeaf.keys, leaf.keys[mid:]...)
    leaf.keys = leaf.keys[:mid]

    // maintain linked list
    newLeaf.next = leaf.next
    leaf.next = newLeaf

    return newLeaf.keys[0], newLeaf
}

// insertInternal inserts key into internal node, returns (promoted_key, new_sibling)
func insertInternal(node *BPlusNode, k int) (int, *BPlusNode) {
    // find child to descend into
    i := 0
    for i < len(node.keys) && k >= node.keys[i] {
        i++
    }

    child := node.children[i]
    var promoted int
    var sibling *BPlusNode

    if child.isLeaf {
        promoted, sibling = insertLeaf(child, k)
    } else {
        promoted, sibling = insertInternal(child, k)
    }

    if sibling == nil {
        return 0, nil // no split
    }

    // child split: insert promoted key and sibling into this node
    node.keys = append(node.keys, 0)
    copy(node.keys[i+1:], node.keys[i:])
    node.keys[i] = promoted

    node.children = append(node.children, nil)
    copy(node.children[i+2:], node.children[i+1:])
    node.children[i+1] = sibling

    // no overflow
    if len(node.keys) <= maxKeys {
        return 0, nil
    }

    // split internal node
    mid = len(node.keys) / 2
    promotedUp := node.keys[mid]

    newNode := newBPlusNode(false)
    newNode.keys = append(newNode.keys, node.keys[mid+1:]...)
    newNode.children = append(newNode.children, node.children[mid+1:]...)

    node.keys = node.keys[:mid]
    node.children = node.children[:mid+1]

    return promotedUp, newNode
}

// bpInsert inserts key k into B+ tree, returns new root
func bpInsert(root *BPlusNode, k int) *BPlusNode {
    if root == nil {
        root = newBPlusNode(true)
        root.keys = append(root.keys, k)
        return root
    }

    var promoted int
    var sibling *BPlusNode

    if root.isLeaf {
        promoted, sibling = insertLeaf(root, k)
    } else {
        promoted, sibling = insertInternal(root, k)
    }

    if sibling != nil {
        // root split: create new root
        newRoot := newBPlusNode(false)
        newRoot.keys = append(newRoot.keys, promoted)
        newRoot.children = append(newRoot.children, root, sibling)
        return newRoot
    }

    return root
}

// bpTraverse traverses all keys via leaf linked list
func bpTraverse(root *BPlusNode) []int {
    if root == nil {
        return nil
    }

    node := root
    for !node.isLeaf {
        node = node.children[0]
    }

    result := []int{}
    for node != nil {
        result = append(result, node.keys...)
        node = node.next
    }
    return result
}

// bpPrintLeaves prints leaf nodes with link arrows
func bpPrintLeaves(root *BPlusNode) {
    if root == nil {
        return
    }

    node := root
    for !node.isLeaf {
        node = node.children[0]
    }

    first := true
    for node != nil {
        if !first {
            fmt.Print(" -> ")
        }
        first = false
        fmt.Print("[")
        for i, k := range node.keys {
            if i > 0 {
                fmt.Print(",")
            }
            fmt.Print(k)
        }
        fmt.Print("]")
        node = node.next
    }
    fmt.Println()
}

// bpRangeQuery finds all keys in [lo, hi]
func bpRangeQuery(root *BPlusNode, lo, hi int) []int {
    result := []int{}
    if root == nil {
        return result
    }

    node := findLeaf(root, lo)
    for node != nil {
        for _, k := range node.keys {
            if k > hi {
                return result
            }
            if k >= lo {
                result = append(result, k)
            }
        }
        node = node.next
    }
    return result
}

func main() {
    var root *BPlusNode

    // insert sequence
    keys := []int{10, 20, 5, 15, 25, 30, 35, 40}
    fmt.Print("Insert sequence: ")
    for _, k := range keys {
        fmt.Printf("%d ", k)
        root = bpInsert(root, k)
    }
    fmt.Println()
    fmt.Println()

    // show leaf structure
    fmt.Print("Leaf nodes (linked list): ")
    bpPrintLeaves(root)

    // traverse all keys
    fmt.Print("Traversal: ")
    for i, k := range bpTraverse(root) {
        if i > 0 {
            fmt.Print(" ")
        }
        fmt.Print(k)
    }
    fmt.Println()

    // search
    fmt.Printf("Search 15: %v\n", map[bool]string{true: "Found", false: "Not found"}[bpSearch(root, 15)])
    fmt.Printf("Search 12: %v\n", map[bool]string{true: "Found", false: "Not found"}[bpSearch(root, 12)])

    // range query
    fmt.Print("Range query [15, 35]: ")
    for i, k := range bpRangeQuery(root, 15, 35) {
        if i > 0 {
            fmt.Print(" ")
        }
        fmt.Print(k)
    }
    fmt.Println()
}

运行该程序将输出:

Insert sequence: 10 20 5 15 25 30 35 40

Leaf nodes (linked list): [5, 10] -> [15, 20] -> [25, 30] -> [35, 40]
Traversal: 5 10 15 20 25 30 35 40
Search 15: Found
Search 12: Not found
Range query [15, 35]: 15 20 25 30 35

四个语言版本的输出结果完全一致。分析输出结果:

  • 叶子节点链表 [5,10] -> [15,20] -> [25,30] -> [35,40] 展示了B+树的核心特征:所有数据存储在叶子节点中,叶子节点通过链表串联
  • 遍历结果 5 10 15 20 25 30 35 40 是所有关键字的升序排列,由叶子链表直接给出
  • 搜索 15 成功找到(在叶子 [15,20] 中),搜索 12 未找到(不存在)
  • 范围查询 [15, 35] 从叶子 [15,20] 开始沿链表遍历,依次收集 15, 20, 25, 30, 35,遇到 40 > 35 停止

插入 10, 20, 5, 15, 25, 30, 35, 40(MAX=3)后,最终的树结构为:

                    [20]
                   /    \
              [10]        [30]
             /    \      /    \
       [5,10]->[15,20]->[25,30]->[35,40]
  • 根节点 [20] 有 1 个关键字,2 个子节点
  • 第二层 [10][30] 是内部节点,仅存路由关键字
  • 底层是 4 个叶子节点,通过链表 -> 串联
  • 所有叶子节点都在同一深度,所有数据集中在叶子中

B+树的性质(总结)

B+树通过将数据集中在叶子节点并用链表串联,在B树的平衡性基础上增加了高效的范围查询能力,使其成为数据库和文件系统索引的标准选择。

时间复杂度

操作 时间复杂度 说明
搜索(Search) O(log n) 必须走到叶子节点,路径长度固定为树高
插入(Insert) O(log n) 找到叶子 + 分裂传播,最多传播到根
删除(Delete) O(log n) 找到叶子 + 合并或借用,最多传播到根
范围查询(Range Query) O(log n + k) O(log n) 定位起点 + O(k) 沿链表遍历 k 个结果
遍历(Traversal) O(n) 沿叶子链表遍历全部关键字

B+树 vs B树 对比

特性 B树 B+树
数据位置 所有节点 仅叶子节点
内部节点内容 关键字 + 数据 仅路由关键字
叶子链接 链表串联
节点扇出 较小 较大(内部节点不存数据)
树高 较高 较矮
单次查找 可提前终止 必须到叶子
范围查询 O(n log n) 中序遍历 O(log n + k) 链表遍历
顺序遍历 需递归中序 链表直接遍历
磁盘 I/O 较多 较少(树更矮)

磁盘访问优势

B+树比B树更适合磁盘存储的原因:

  • 更大的扇出(Fan-out):内部节点不存储数据,同样大小的磁盘页可以容纳更多关键字,树更矮
  • 更少的磁盘 I/O:树高更矮意味着查找路径更短,磁盘读取次数更少
  • 稳定的时间复杂度:每次查找必须走到叶子,路径长度一致,性能可预测
  • 高效的范围查询:链表结构使得范围扫描只需顺序读取,无需反复遍历树

实际中,当节点大小等于磁盘页大小(通常 4KB)时,一棵 B+树存储 10 亿条记录只需约 3~4 层,即 3~4 次磁盘读取即可完成查找。

实际应用

应用 说明
MySQL InnoDB 默认索引结构,主键索引和二级索引均使用 B+树
PostgreSQL 默认索引结构,支持多种索引类型,B+树最常用
SQLite 使用 B+树变体作为表和索引的存储结构
NTFS Windows 文件系统的目录索引
ext4 Linux 文件系统的 Htree 目录索引
Oracle Database B-tree 索引的默认实现本质上是 B+树
posted @ 2026-04-16 21:00  游翔  阅读(13)  评论(0)    收藏  举报