B树的分裂与合并操作详解

B树的分裂与合并操作详解

引言

B树是一种自平衡的树数据结构,广泛应用于数据库和文件系统中,以维持有序数据并允许高效的插入、删除和搜索操作。作为一种多路搜索树,B树的每个节点可以有多个子节点和多个键值。B树的关键特性在于其通过分裂和合并操作来保持平衡。本文将详细介绍B树的分裂与合并操作,并提供具体的源码示例。

B树基础知识

在讨论B树的分裂与合并操作之前,先简要回顾一下B树的基本概念和性质。

B树的定义

B树是一种平衡的多路搜索树,满足以下性质:

  1. 每个节点最多有m个子节点(称为m阶B树)。
  2. 除根节点外,每个节点至少有⌈m/2⌉个子节点。
  3. 根节点至少有两个子节点(如果非叶子节点)。
  4. 每个节点包含k-1个键值和k个子节点指针,其中⌈m/2⌉ ≤ k ≤ m
  5. 所有叶子节点都位于同一层。
B树的结构

B树节点的结构如下所示:

Node:
    keys: [k1, k2, ..., kn]
    children: [c0, c1, ..., cn]
    n: 当前节点中的键值数量
    leaf: 是否为叶子节点

B树的分裂操作

分裂操作是B树在插入过程中用于保持平衡的关键操作。当一个节点中的键值数量达到最大值m-1时,该节点需要分裂为两个节点,并将中间键提升到父节点中。

分裂操作的步骤
  1. 创建一个新的节点,作为分裂后的一半。
  2. 将中间键及其左侧的键值和子节点保留在原节点中。
  3. 将中间键右侧的键值和子节点移动到新节点中。
  4. 将中间键提升到父节点中。如果父节点也满了,递归执行分裂操作。
分裂操作的示例

假设我们有一个B树节点,包含以下键值:

Node:
    keys: [10, 20, 30, 40, 50]
    children: [c0, c1, c2, c3, c4, c5]
    n: 5
    leaf: False

在插入新键值60时,节点已满,需要进行分裂操作:

  1. 创建一个新节点:

    New Node:
    keys: []
    children: []
    n: 0
    leaf: False

  2. 将中间键及其左侧键值保留在原节点中:

    Original Node (after split):
    keys: [10, 20]
    children: [c0, c1, c2]
    n: 2
    leaf: False

  3. 将中间键右侧的键值和子节点移动到新节点中:

    New Node (after split):
    keys: [40, 50, 60]
    children: [c3, c4, c5, c6]
    n: 3
    leaf: False

  4. 将中间键30提升到父节点中:

    Parent Node (after split):
    keys: [30]
    children: [Original Node, New Node]
    n: 1
    leaf: False

B树的合并操作

合并操作是B树在删除过程中用于保持平衡的关键操作。当一个节点中的键值数量低于最小值⌈m/2⌉-1时,该节点需要与其相邻的兄弟节点合并,或者从父节点借一个键值。

合并操作的步骤
  1. 检查节点的左兄弟和右兄弟,选择一个键值数量大于最小值的兄弟节点。
  2. 将父节点中的键值下移到当前节点中。
  3. 将兄弟节点中的键值上移到父节点中。
  4. 如果兄弟节点的键值数量也低于最小值,合并当前节点和兄弟节点,并递归执行合并操作。
合并操作的示例

假设我们有一个B树节点,包含以下键值:

Parent Node:
    keys: [20, 40]
    children: [Node1, Node2, Node3]
    n: 2
    leaf: False

删除节点Node2中的键值30后,节点中的键值数量低于最小值,需要进行合并操作:

  1. 检查左兄弟Node1和右兄弟Node3,选择一个键值数量大于最小值的兄弟节点。假设选择右兄弟Node3:

    Node3:
    keys: [50, 60]
    children: [c5, c6, c7]
    n: 2
    leaf: False

  2. 将父节点中的键值40下移到当前节点Node2中:

    Node2 (after borrow):
    keys: [40]
    children: [c3, c4]
    n: 1
    leaf: False

  3. 将右兄弟Node3中的键值50上移到父节点中:

    Parent Node (after borrow):
    keys: [20, 50]
    children: [Node1, Node2, Node3]
    n: 2
    leaf: False

如果右兄弟Node3的键值数量也低于最小值,则需要进行合并操作:

  1. 将当前节点Node2与右兄弟Node3合并:

    Node2 (after merge):
    keys: [40, 50, 60]
    children: [c3, c4, c5, c6, c7]
    n: 3
    leaf: False

  2. 将父节点中的键值50下移到当前节点Node2中,删除右兄弟Node3:

    Parent Node (after merge):
    keys: [20]
    children: [Node1, Node2]
    n: 1
    leaf: False

源码示例

以下是B树的分裂与合并操作的完整源码示例,使用Python语言实现。

python 复制代码
class BTreeNode:
    def __init__(self, t, leaf=False):
        self.t = t  # B树的最小度数
        self.keys = []
        self.children = []
        self.leaf = leaf

class BTree:
    def __init__(self, t):
        self.root = BTreeNode(t, True)
        self.t = t

    def insert(self, k):
        root = self.root
        if len(root.keys) == 2 * self.t - 1:
            s = BTreeNode(self.t, False)
            s.children.append(self.root)
            self.split_child(s, 0)
            self.root = s
            self.insert_non_full(s, k)
        else:
            self.insert_non_full(root, k)

    def insert_non_full(self, x, k):
        i = len(x.keys) - 1
        if x.leaf:
            x.keys.append(None)
            while i >= 0 and k < x.keys[i]:
                x.keys[i + 1] = x.keys[i]
                i -= 1
            x.keys[i + 1] = k
        else:
            while i >= 0 and k < x.keys[i]:
                i -= 1
            i += 1
            if len(x.children[i].keys) == 2 * self.t - 1:
                self.split_child(x, i)
                if k > x.keys[i]:
                    i += 1
            self.insert_non_full(x.children[i], k)

    def split_child(self, x, i):
        t = self.t
        y = x.children[i]
        z = BTreeNode(t, y.leaf)
        x.children.insert(i + 1, z)
        x.keys.insert(i, y.keys[t - 1])

        z.keys = y.keys[t:]
        y.keys = y.keys[:t - 1]

        if not y.leaf:
            z.children = y.children[t:]
            y.children = y.children[:t]

    def delete(self, k):
        self._delete(self.root, k)
        if len(self.root.keys) == 0 and not self.root.leaf:
            self.root = self.root.children[0]

    def _delete(self, x, k):
        t = self.t
        if k in x.keys:
            if x.leaf:
                x.keys.remove(k)
            else:
                idx = x.keys.index(k)
                if len(x.children[idx].keys) >= t:
                    predecessor = self.get_predecessor(x, idx)
                    x.keys[idx] = predecessor
                    self._delete(x.children[idx], predecessor)
                elif len(x.children[idx + 1].keys) >= t:
                    successor = self.get_successor(x, idx)
                    x.keys[idx] = successor


                    self._delete(x.children[idx + 1], successor)
                else:
                    self.merge(x, idx)
                    self._delete(x.children[idx], k)
        else:
            if x.leaf:
                return

            idx = self.find_key(x, k)
            if len(x.children[idx].keys) < t:
                if idx > 0 and len(x.children[idx - 1].keys) >= t:
                    self.borrow_from_prev(x, idx)
                elif idx < len(x.children) - 1 and len(x.children[idx + 1].keys) >= t:
                    self.borrow_from_next(x, idx)
                else:
                    if idx != len(x.children) - 1:
                        self.merge(x, idx)
                    else:
                        self.merge(x, idx - 1)

            self._delete(x.children[idx], k)

    def get_predecessor(self, x, idx):
        current = x.children[idx]
        while not current.leaf:
            current = current.children[-1]
        return current.keys[-1]

    def get_successor(self, x, idx):
        current = x.children[idx + 1]
        while not current.leaf:
            current = current.children[0]
        return current.keys[0]

    def merge(self, x, idx):
        t = self.t
        child = x.children[idx]
        sibling = x.children[idx + 1]

        child.keys.append(x.keys[idx])
        child.keys.extend(sibling.keys)

        if not child.leaf:
            child.children.extend(sibling.children)

        x.keys.pop(idx)
        x.children.pop(idx + 1)

    def borrow_from_prev(self, x, idx):
        child = x.children[idx]
        sibling = x.children[idx - 1]

        child.keys.insert(0, x.keys[idx - 1])
        x.keys[idx - 1] = sibling.keys.pop()

        if not child.leaf:
            child.children.insert(0, sibling.children.pop())

    def borrow_from_next(self, x, idx):
        child = x.children[idx]
        sibling = x.children[idx + 1]

        child.keys.append(x.keys[idx])
        x.keys[idx] = sibling.keys.pop(0)

        if not child.leaf:
            child.children.append(sibling.children.pop(0))

    def find_key(self, x, k):
        idx = 0
        while idx < len(x.keys) and x.keys[idx] < k:
            idx += 1
        return idx

    def traverse(self, x=None, depth=0):
        if x is None:
            x = self.root
        print(" " * depth, x.keys)
        if not x.leaf:
            for child in x.children:
                self.traverse(child, depth + 4)

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

# 测试B树
btree = BTree(3)

for i in range(10):
    btree.insert(i)

print("Initial tree:")
btree.traverse()

btree.delete(3)
print("\nTree after deleting 3:")
btree.traverse()

btree.delete(6)
print("\nTree after deleting 6:")
btree.traverse()

结论

通过本文的详细介绍,我们理解了B树的分裂与合并操作的原理和实现方式。分裂操作在插入过程中用于保持B树的平衡,而合并操作在删除过程中用于保持B树的平衡。这些操作确保了B树能够高效地执行插入、删除和搜索操作。通过源码示例,我们展示了如何在实际编程中实现这些操作,并测试了它们的正确性。希望本文对读者深入理解B树的工作机制有所帮助,并能够在实际项目中应用B树。

相关推荐
学不会•2 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜4 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow4 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
搬砖的小码农_Sky6 小时前
C语言:数组
c语言·数据结构
沉默璇年7 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder7 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript