Bootstrap

B树的插入与删除操作详解

B树的插入与删除操作详解

引言

B树(B-Tree)是一种自平衡的树数据结构,广泛应用于数据库和文件系统中。其主要特点是能够在保证数据有序的同时,提供高效的插入、删除和查找操作。本文将详细介绍B树的插入与删除操作,并结合具体的源码进行讲解。

B树的基本概念

在深入探讨B树的插入与删除操作之前,我们先回顾一下B树的基本概念和性质:

  1. 每个节点包含多个键和子节点指针
  2. 所有叶子节点在同一层
  3. 节点的键按递增顺序排列
  4. 每个节点至少包含t-1个键,最多包含2t-1个键(t是B树的阶数)。
B树插入操作

插入操作是B树的基本操作之一,分为两种情况:不需要分裂节点和需要分裂节点。具体步骤如下:

  1. 寻找插入位置:从根节点开始,递归寻找适合插入新键的位置。
  2. 插入新键:如果找到的节点未满,直接插入新键;如果节点已满,分裂节点后再插入。

以下是B树插入操作的具体实现(Python代码):

class BTreeNode:
    def __init__(self, t, leaf=False):
        self.t = t  # 阶数
        self.leaf = leaf  # 是否为叶子节点
        self.keys = []  # 存储键
        self.children = []  # 存储子节点指针

class BTree:
    def __init__(self, t):
        self.t = t  # 阶数
        self.root = BTreeNode(t, True)  # 初始化根节点

    def insert(self, key):
        root = self.root
        if len(root.keys) == 2 * self.t - 1:  # 如果根节点已满,进行分裂
            new_node = BTreeNode(self.t)
            new_node.children.append(self.root)
            new_node.leaf = False
            self.split_child(new_node, 0)
            self.root = new_node
        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
        else:
            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
        node = parent.children[i]
        new_node = BTreeNode(t, node.leaf)
        parent.children.insert(i + 1, new_node)
        parent.keys.insert(i, node.keys[t - 1])
        new_node.keys = node.keys[t:(2 * t - 1)]
        node.keys = node.keys[0:(t - 1)]
        if not node.leaf:
            new_node.children = node.children[t:(2 * t)]
            node.children = node.children[0:t]
插入操作详解
  1. 寻找插入位置

    • 从根节点开始,根据键值与节点中的键进行比较,递归向下寻找合适的插入位置。
    • 如果当前节点是叶子节点,直接在节点中插入新键;如果是非叶子节点,继续递归寻找。
  2. 插入新键

    • 如果找到的节点未满(即节点中的键数小于2t-1),直接将新键插入到节点的合适位置。
    • 如果找到的节点已满(即节点中的键数等于2t-1),首先进行节点分裂,将节点分为两个包含t-1个键的新节点,并将中间键提升到父节点。
  3. 节点分裂

    • 分裂节点时,将满节点的中间键提升到父节点,并将节点分为两个包含t-1个键的新节点。
    • 如果父节点也已满,需要递归向上分裂,直至根节点。如果根节点分裂,会产生新的根节点,树的高度增加。
B树删除操作

删除操作是B树中较为复杂的操作,需要考虑多种情况。删除操作的基本步骤如下:

  1. 寻找删除位置:从根节点开始,递归寻找待删除键所在的节点。
  2. 删除键:根据不同情况进行处理,包括直接删除、合并节点、借用兄弟节点等。

以下是B树删除操作的具体实现(Python代码):

class BTree:
    # 省略其他方法

    def delete(self, key):
        if not self.root:
            return
        self.delete_key(self.root, key)
        if len(self.root.keys) == 0:
            if self.root.leaf:
                self.root = None
            else:
                self.root = self.root.children[0]

    def delete_key(self, node, key):
        t = self.t
        i = 0
        while i < len(node.keys) and key > node.keys[i]:
            i += 1
        if i < len(node.keys) and key == node.keys[i]:
            if node.leaf:
                node.keys.pop(i)
            else:
                self.delete_non_leaf(node, i)
        else:
            if node.leaf:
                return
            flag = (i == len(node.keys))
            if len(node.children[i].keys) < t:
                self.fill(node, i)
            if flag and i > len(node.keys):
                self.delete_key(node.children[i - 1], key)
            else:
                self.delete_key(node.children[i], key)

    def delete_non_leaf(self, node, i):
        key = node.keys[i]
        if len(node.children[i].keys) >= self.t:
            pred = self.get_predecessor(node, i)
            node.keys[i] = pred
            self.delete_key(node.children[i], pred)
        elif len(node.children[i + 1].keys) >= self.t:
            succ = self.get_successor(node, i)
            node.keys[i] = succ
            self.delete_key(node.children[i + 1], succ)
        else:
            self.merge(node, i)
            self.delete_key(node.children[i], key)

    def get_predecessor(self, node, i):
        cur = node.children[i]
        while not cur.leaf:
            cur = cur.children[len(cur.keys)]
        return cur.keys[len(cur.keys) - 1]

    def get_successor(self, node, i):
        cur = node.children[i + 1]
        while not cur.leaf:
            cur = cur.children[0]
        return cur.keys[0]

    def fill(self, node, i):
        if i != 0 and len(node.children[i - 1].keys) >= self.t:
            self.borrow_from_prev(node, i)
        elif i != len(node.keys) and len(node.children[i + 1].keys) >= self.t:
            self.borrow_from_next(node, i)
        else:
            if i != len(node.keys):
                self.merge(node, i)
            else:
                self.merge(node, i - 1)

    def borrow_from_prev(self, node, i):
        child = node.children[i]
        sibling = node.children[i - 1]
        for j in range(len(child.keys) - 1, -1, -1):
            child.keys[j + 1] = child.keys[j]
        if not child.leaf:
            for j in range(len(child.children) - 1, -1, -1):
                child.children[j + 1] = child.children[j]
        child.keys[0] = node.keys[i - 1]
        if not child.leaf:
            child.children[0] = sibling.children[len(sibling.keys)]
        node.keys[i - 1] = sibling.keys.pop()
        if not child.leaf:
            sibling.children.pop()

    def borrow_from_next(self, node, i):
        child = node.children[i]
        sibling = node.children[i + 1]
        child.keys.append(node.keys[i])
        if not child.leaf:
            child.children.append(sibling.children[0])
        node.keys[i] = sibling.keys[0]
        sibling.keys.pop(0)
        if not child.leaf:
            sibling.children.pop(0)

    def merge(self, node, i):
        child = node.children[i]
        sibling = node.children[i + 1]
        child.keys.append(node.keys[i])
        for j in range(len(sibling.keys)):
            child.keys.append(sibling.keys[j])
        if not child.leaf:
            for j in range(len(sibling.children)):
                child.children.append(sibling.children[j])
        node.keys.pop(i)
        node.children.pop(i + 1)
删除操作详解(续)
  1. 寻找删除位置

    • 从根节点开始,根据键值与节点中的键进行比较,递归向下寻找待删除键所在的节点。
    • 如果当前节点是叶子节点,直接在节点中删除键;如果是非叶子节点,根据不同情况处理。
  2. 删除键

    • 叶子节点:如果待删除键在叶子节点中,直接删除键。
    • 非叶子节点
      • 如果待删除键在非叶子节点中,并且该键前的子节点的键数大于等于t,从该子节点中找到前驱键替代待删除键,并递归删除前驱键。
      • 如果待删除键在非叶子节点中,并且该键后的子节点的键数大于等于t,从该子节点中找到后继键替代待删除键,并递归删除后继键。
      • 如果待删除键在非叶子节点中,并且该键前后子节点的键数均小于t,将待删除键和后继子节点合并,然后递归删除合并后的节点中的待删除键。
  3. 节点借用

    • 向前兄弟节点借用:如果当前节点的前兄弟节点的键数大于等于t,从前兄弟节点借一个键到当前节点,并调整当前节点和前兄弟节点的键和子节点。
    • 向后兄弟节点借用:如果当前节点的后兄弟节点的键数大于等于t,从后兄弟节点借一个键到当前节点,并调整当前节点和后兄弟节点的键和子节点。
  4. 节点合并

    • 合并兄弟节点:如果当前节点的键数小于t,并且兄弟节点的键数也小于t,将当前节点与兄弟节点合并,并调整父节点的键和子节点。
示例

为了更好地理解B树的插入与删除操作,我们通过一个具体示例来演示这些操作。

假设我们有一个阶数为3的B树,插入以下键:10, 20, 5, 6, 12, 30, 7, 17

插入操作

  1. 插入10:

    • B树为空,创建一个包含键10的根节点。
  2. 插入20:

    • 将20插入根节点。
  3. 插入5:

    • 将5插入根节点,键顺序为:5, 10, 20。
  4. 插入6:

    • 将6插入根节点,键顺序为:5, 6, 10, 20。
  5. 插入12:

    • 将12插入根节点,键顺序为:5, 6, 10, 12, 20。
  6. 插入30:

    • 根节点已满,进行分裂。分裂后,根节点为10,左子节点为5, 6,右子节点为12, 20, 30。
  7. 插入7:

    • 将7插入左子节点,键顺序为:5, 6, 7。
  8. 插入17:

    • 将17插入右子节点,键顺序为:12, 17, 20, 30。

删除操作

  1. 删除6:

    • 6在叶子节点中,直接删除。
  2. 删除13:

    • 13不存在,操作无效。
  3. 删除7:

    • 7在叶子节点中,直接删除。
  4. 删除12:

    • 12在非叶子节点中,替换为前驱键10,并删除键10。
  5. 删除10:

    • 10在非叶子节点中,替换为前驱键5,并删除键5。
代码示例

以下是一个完整的Python示例,包括插入和删除操作:

class BTreeNode:
    def __init__(self, t, leaf=False):
        self.t = t
        self.leaf = leaf
        self.keys = []
        self.children = []

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

    def insert(self, key):
        root = self.root
        if len(root.keys) == 2 * self.t - 1:
            new_node = BTreeNode(self.t)
            new_node.children.append(self.root)
            new_node.leaf = False
            self.split_child(new_node, 0)
            self.root = new_node
        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
        else:
            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
        node = parent.children[i]
        new_node = BTreeNode(t, node.leaf)
        parent.children.insert(i + 1, new_node)
        parent.keys.insert(i, node.keys[t - 1])
        new_node.keys = node.keys[t:(2 * t - 1)]
        node.keys = node.keys[0:(t - 1)]
        if not node.leaf:
            new_node.children = node.children[t:(2 * t)]
            node.children = node.children[0:t]

    def delete(self, key):
        if not self.root:
            return
        self.delete_key(self.root, key)
        if len(self.root.keys) == 0:
            if self.root.leaf:
                self.root = None
            else:
                self.root = self.root.children[0]

    def delete_key(self, node, key):
        t = self.t
        i = 0
        while i < len(node.keys) and key > node.keys[i]:
            i += 1
        if i < len(node.keys) and key == node.keys[i]:
            if node.leaf:
                node.keys.pop(i)
            else:
                self.delete_non_leaf(node, i)
        else:
            if node.leaf:
                return
            flag = (i == len(node.keys))
            if len(node.children[i].keys) < t:
                self.fill(node, i)
            if flag and i > len(node.keys):
                self.delete_key(node.children[i - 1], key)
            else:
                self.delete_key(node.children[i], key)

    def delete_non_leaf(self, node, i):
        key = node.keys[i]
        if len(node.children[i].keys) >= self.t:
            pred = self.get_predecessor(node, i)
            node.keys[i] = pred
            self.delete_key(node.children[i], pred)
        elif len(node.children[i + 1].keys) >= self.t:
            succ = self.get_successor(node, i)
            node.keys[i] = succ
            self.delete_key(node.children[i + 1], succ)
        else:
            self.merge(node, i)
            self.delete_key(node.children[i], key)

    def get_predecessor(self, node, i):
        cur = node.children[i]
        while not cur.leaf:
            cur = cur.children[len(cur.keys)]
        return cur.keys[len(cur.keys) - 1]

    def get_successor(self, node, i):
        cur = node.children[i + 1]
        while not cur.leaf:
            cur = cur.children[0]
        return cur.keys[0]

    def fill(self, node, i):
        if i != 0 and len(node.children[i - 1].keys) >= self.t:
            self.borrow_from_prev(node, i)
        elif i != len(node.keys) and len(node.children[i + 1].keys) >= self.t:
            self.borrow_from_next(node, i)
        else:
            if i != len(node.keys):
                self.merge(node, i)
            else:
                self.merge(node, i - 1)

    def borrow_from_prev(self, node, i):
        child = node.children[i]
        sibling = node.children[i - 1]
        for j in range(len(child.keys) - 1, -1, -1):
            child.keys[j + 1] = child.keys[j]
        if not child.leaf:
            for j in range(len(child.children) - 1, -1, -1):
                child.children[j + 1] = child.children[j]
        child.keys[0] = node.keys[i - 1]
        if not child.leaf:
            child.children[0] = sibling.children[len(sibling.keys)]
        node.keys[i - 1] = sibling.keys.pop()
        if not child.leaf:
            sibling.children.pop()

    def borrow_from_next(self, node, i):
        child = node.children[i]
        sibling = node.children[i + 1]
        child.keys.append(node.keys

[i])
        if not child.leaf:
            child.children.append(sibling.children[0])
        node.keys[i] = sibling.keys[0]
        sibling.keys.pop(0)
        if not child.leaf:
            sibling.children.pop(0)

    def merge(self, node, i):
        child = node.children[i]
        sibling = node.children[i + 1]
        child.keys.append(node.keys[i])
        for j in range(len(sibling.keys)):
            child.keys.append(sibling.keys[j])
        if not child.leaf:
            for j in range(len(sibling.children)):
                child.children.append(sibling.children[j])
        node.keys.pop(i)
        node.children.pop(i + 1)
总结

本文详细介绍了B树的插入和删除操作,并通过具体的代码实现了这些操作。B树的插入操作涉及寻找合适的插入位置,并在必要时分裂节点;删除操作则更为复杂,需要处理多种情况,包括直接删除、借用兄弟节点和合并节点。希望通过本文的讲解,读者能够深入理解B树的插入和删除操作,并能够应用到实际的开发中。

;