数据结构---B+树

发布于:2025-07-08 ⋅ 阅读:(21) ⋅ 点赞:(0)

数据结构—B+树

B+树是一种平衡的多路查找树,是数据库和文件系统中常用的索引结构。以下是对B+树的详细介绍:

一、基本结构特点

  1. 节点类型
    • B+树有内节点(非叶子节点)和叶子节点。内节点用于存储索引键和指向下一层节点的指针。例如,在一个包含数字键的B+树中,内节点可能存储键值为50、100等,这些键将数据范围划分成不同的区间,每个键对应一个指针指向子树。而叶子节点用于存储实际的数据记录指针(或者数据本身,这取决于具体的实现)。
    • 所有叶子节点都位于同一层,它们之间通过指针相互连接,形成一个有序的链表。这种结构使得范围查询非常高效。比如,要查找键值在10到20之间的所有记录,可以从第一个大于或等于10的叶子节点开始,沿着叶子节点链表顺序查找,直到遇到大于20的键值为止。
  2. 节点的键值和子节点数量限制
    • 假设B+树的阶数为m,那么内节点最多有m个子节点,最少有[m/2]个子节点(根节点除外,根节点至少有2个子节点,除非它是一个叶子节点)。对于叶子节点,最多可以存储m - 1个键值,最少存储[m/2] - 1个键值(这里m是B+树的阶数)。
    • 例如,一个阶数为4的B+树,内节点最多有4个子节点,最少有2个子节点(根节点除外)。叶子节点最多存储3个键值,最少存储1个键值。这种限制保证了B+树的平衡性,使得查找、插入和删除操作的性能相对稳定。
  3. 键值的存储顺序
    • 在B+树的每个节点中,键值是按照从小到大(或从大到小)的顺序存储的。在内节点中,键值用于划分子树的范围。例如,一个内节点的键值为{20,40,60},那么它的子树范围可以分为:小于20的键值在第一个子树中,20到40之间的键值在第二个子树中,40到60之间的键值在第三个子树中,大于60的键值在第四个子树中。在叶子节点中,键值也是有序存储,方便范围查询。

二、B+树的操作

  1. 查找操作
    • 从根节点开始,根据目标键值与节点中键值的比较结果,沿着相应的指针向下查找。如果目标键值小于当前节点的第一个键值,就沿着第一个指针向下查找;如果目标键值在两个键值之间,就沿着中间的指针向下查找;如果目标键值大于当前节点的最后一个键值,就沿着最后一个指针向下查找。
    • 一直查找直到叶子节点,如果在叶子节点中找到目标键值,就返回相应的数据记录指针;如果没有找到,就返回查找失败的结果。查找操作的时间复杂度为O(logn),其中n是B+树中叶子节点的数量。因为B+树是平衡的,每次查找都会将查找范围缩小到一个子树,这种对半查找的思想使得查找效率很高。
  2. 插入操作
    • 首先按照查找操作的方式找到目标键值应该插入的叶子节点位置。如果叶子节点没有满(即键值数量没有达到最大限制),直接将键值插入到合适的位置,并保持叶子节点中键值的有序性。
    • 如果叶子节点满了,就需要进行分裂操作。将叶子节点分裂成两个叶子节点,其中一个节点存储原节点中较小的一部分键值,另一个节点存储较大的一部分键值。然后将分裂产生的新节点的最小键值(或者最大键值,取决于具体实现)上移至父节点。如果父节点满了,父节点也会进行分裂,这个过程可能会一直向上进行,直到根节点分裂,甚至可能产生一个新的根节点。插入操作的时间复杂度也是O(logn),因为分裂操作虽然会增加一些额外的步骤,但总体上不会改变操作的对数级别复杂度。
  3. 删除操作
    • 先找到要删除的键值所在的叶子节点。如果删除键值后叶子节点仍然满足最小键值数量限制,直接删除该键值即可。
    • 如果删除键值后叶子节点不满足最小键值数量限制,需要进行调整。可以从相邻的兄弟节点借一个键值来满足最小键值数量限制。如果相邻兄弟节点也满足最小键值数量限制,无法借键值,就需要将两个兄弟节点合并。合并后,父节点中的相应键值会被删除。如果父节点不满足最小键值数量限制,这个调整过程可能会一直向上进行,甚至可能导致根节点被删除,树的高度减小。删除操作的时间复杂度同样是O(logn)。

三、B+树的优点

  1. 适合磁盘存储
    • B+树的结构使得它能够很好地利用磁盘的顺序读写特性。因为B+树的内节点只存储键值和指针,而数据记录存储在叶子节点,叶子节点通过指针连接成链表。当进行范围查询时,可以顺序读取叶子节点中的数据,减少了磁盘的随机访问次数,从而提高了查询效率。在数据库系统中,数据通常存储在磁盘上,B+树的这种特性使得它成为理想的索引结构。
  2. 高效的范围查询
    • 由于叶子节点是有序的,并且通过指针连接,范围查询可以直接从一个叶子节点开始,沿着链表顺序查找,直到满足条件为止。相比之下,其他一些索引结构(如B树)在范围查询时可能需要多次回溯或者访问多个不连续的节点,B+树在这一点上具有明显优势。
  3. 平衡性好
    • B+树通过严格的节点键值和子节点数量限制,保证了树的平衡性。无论进行插入、删除操作,树的高度变化都不会太大,这使得各种操作的时间复杂度都能保持在对数级别,保证了操作的高效性。

四、B+树的应用场景

  1. 数据库索引
    • 在关系型数据库中,B+树被广泛用作主索引和辅助索引。例如,在一个学生信息表中,如果以学号作为主键建立索引,B+树可以快速定位到特定学号的学生记录。同时,也可以以姓名等字段建立辅助索引,通过B+树实现快速的姓名查找和范围查询(如查找姓氏为“张”的学生)。
  2. 文件系统
    • 在文件系统中,B+树可以用于管理文件的索引。它可以快速定位文件在磁盘上的存储位置,同时支持对文件名等属性的范围查询。例如,在一个大型的文件服务器中,通过B+树可以快速找到某个目录下所有以特定前缀命名的文件。

五、代码示例

5.1 数据库索引中的B+树代码示例(C++)

以下是一个简单的B+树插入操作的C++代码实现

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

const int M = 4; // B+树的阶数
const int MM = 2 * M;
const int L = 5; // 叶节点能容纳的最大键值对数

struct Node {
    bool leaf; // 是否为叶节点
    Node* parent; // 父节点
    Node* children[MM + 1]; // 子节点
    int values[MM]; // 记录值
    int cnt; // 子节点数或者键值对数
};

class BPlusTree {
public:
    BPlusTree();
    ~BPlusTree();
    void insert(int value); // 插入记录
    void show(); // 显示整棵B+树

private:
    void split(Node* x, int idx); // 对满节点分裂
    void insertNonfull(Node* x, int value); // 插入到非满节点
    void show(Node* x, int num); // 递归显示某个节点及其子树
    Node* root; // 根节点
};

BPlusTree::BPlusTree() {
    root = NULL;
}

BPlusTree::~BPlusTree() {
    // TODO
}

void BPlusTree::split(Node* x, int idx) {
    Node* y = new Node();
    Node* z = x->children[idx];
    y->leaf = z->leaf;
    y->cnt = L;
    z->cnt = L;

    // 将z节点的后L个值移动到y节点
    memcpy(y->values, z->values + L, sizeof(int) * L);
    if (!y->leaf) {
        memcpy(y->children, z->children + L, sizeof(Node*) * (L + 1));
        for (int i = L; i < MM; i++) {
            z->children[i] = NULL;
        }
    }
    z->cnt = L;

    // 为父节点x插入键值,并将y节点加入到x的子节点中
    for (int i = x->cnt + 1; i > idx + 1; i--) {
        x->children[i] = x->children[i - 1];
    }
    x->children[idx + 1] = y;
    for (int i = x->cnt; i > idx; i--) {
        x->values[i] = x->values[i - 1];
    }
    x->values[idx] = z->values[L];
    x->cnt++;
}

void BPlusTree::insertNonfull(Node* x, int value) {
    int i = x->cnt - 1;

    if (x->leaf) {
        while (i >= 0 && value < x->values[i]) {
            x->values[i + 1] = x->values[i];
            i--;
        }
        x->values[i + 1] = value;
        x->cnt++;
    }
    else {
        while (i >= 0 && value < x->values[i]) {
            i--;
        }
        i++;
        if (x->children[i]->cnt == MM) {
            split(x, i);
            if (value > x->values[i]) {
                i++;
            }
        }
        insertNonfull(x->children[i], value);
    }
}

void BPlusTree::insert(int value) {
    if (root == NULL) {
        root = new Node();
        root->leaf = true;
        root->cnt = 1;
        root->values[0] = value;
    }
    else {
        if (root->cnt == MM) {
            Node* s = new Node();
            s->leaf = false;
            s->children[0] = root;
            split(s, 0);
            root = s;
            if (value > root->values[0]) {
                insertNonfull(root->children[1], value);
            }
            else {
                insertNonfull(root->children[0], value);
            }
        }
        else {
            insertNonfull(root, value);
        }
    }
}

void BPlusTree::show() {
    if (root != NULL) {
        show(root, 0);
    }
}

void BPlusTree::show(Node* x, int num) {
    cout << "(" << num << ")";
    for (int i = 0; i < x->cnt; i++) {
        cout << x->values[i] << " ";
    }
    cout << endl;
    if (!x->leaf) {
        for (int i = 0; i <= x->cnt; i++) {
            if (x->children[i] != NULL) {
                show(x->children[i], num + 1);
            }
        }
    }
}

int main() {
    BPlusTree tree;
    for (int i = 0; i < 20; i++) {
        tree.insert(i);
    }
    tree.show();

    return 0;
}

5.2 文件系统中的B+树代码示例(C#)

以下是一个B+树在文件系统中的简单实现,用于管理文件索引

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


public class BPlusTreeNode
{
    public List<int> Keys { get; set; }
    public List<BPlusTreeNode> Children { get; set; }
    public bool IsLeaf { get; set; }
    public BPlusTreeNode Next { get; set; }

    public BPlusTreeNode(bool isLeaf)
    {
        Keys = new List<int>();
        Children = new List<BPlusTreeNode>();
        IsLeaf = isLeaf;
    }
}

public class BPlusTree
{
    private int _minDegree;
    private BPlusTreeNode _root;

    public BPlusTree(int minDegree)
    {
        _minDegree = minDegree;
        _root = new BPlusTreeNode(true);
    }

    public void Insert(int key)
    {
        if (_root.Keys.Count == 2 * _minDegree - 1)
        {
            var newRoot = new BPlusTreeNode(false);
            newRoot.Children.Add(_root);
            SplitChild(newRoot, 0, _root);
            _root = newRoot;
        }
        InsertNonFull(_root, key);
    }

    private void InsertNonFull(BPlusTreeNode node, int key)
    {
        if (node.IsLeaf)
        {
            int i = node.Keys.Count - 1;
            while (i >= 0 && key < node.Keys[i])
            {
                i--;
            }
            node.Keys.Insert(i + 1, key);
        }
        else
        {
            int i = node.Keys.Count - 1;
            while (i >= 0 && key < node.Keys[i])
            {
                i--;
            }
            i++;
            if (node.Children[i].Keys.Count == 2 * _minDegree - 1)
            {
                SplitChild(node, i, node.Children[i]);
                if (key > node.Keys[i])
                {
                    i++;
                }
            }
            InsertNonFull(node.Children[i], key);
        }
    }

    private void SplitChild(BPlusTreeNode parent, int index, BPlusTreeNode fullChild)
    {
        var newNode = new BPlusTreeNode(fullChild.IsLeaf);
        parent.Keys.Insert(index, fullChild.Keys[_minDegree - 1]);
        parent.Children.Insert(index + 1, newNode);
        newNode.Keys.AddRange(fullChild.Keys.GetRange(_minDegree, _minDegree - 1));
        fullChild.Keys.RemoveRange(_minDegree - 1, _minDegree);
        if (!fullChild.IsLeaf)
        {
            newNode.Children.AddRange(fullChild.Children.GetRange(_minDegree, _minDegree));
            fullChild.Children.RemoveRange(_minDegree, _minDegree);
        }
        else
        {
            newNode.Next = fullChild.Next;
            fullChild.Next = newNode;
        }
    }

    public void PrintTree()
    {
        PrintNode(_root, "");
    }

    private void PrintNode(BPlusTreeNode node, string indent)
    {
        Console.WriteLine(indent + string.Join(", ", node.Keys));
        if (!node.IsLeaf)
        {
            foreach (var child in node.Children)
            {
                PrintNode(child, indent + "  ");
            }
        }
    }
}

namespace BPluse_Tree_CSharpTest
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var bpt = new BPlusTree(2);
            bpt.Insert(10);
            bpt.Insert(20);
            bpt.Insert(5);
            bpt.Insert(6);
            bpt.Insert(12);
            bpt.Insert(30);
            bpt.Insert(7);
            bpt.Insert(17);
            bpt.PrintTree();
            Console.ReadKey();
        }
    }
}

好的文章:

1.关于B+树的介绍、用途和c++代码实现

2.红黑树、AVL树、B树、B+树的对比与应用场景解析