B树的插入与删除操作详解
引言
B树(B-Tree)是一种自平衡的树数据结构,广泛应用于数据库和文件系统中。其主要特点是能够在保证数据有序的同时,提供高效的插入、删除和查找操作。本文将详细介绍B树的插入与删除操作,并结合具体的源码进行讲解。
B树的基本概念
在深入探讨B树的插入与删除操作之前,我们先回顾一下B树的基本概念和性质:
- 每个节点包含多个键和子节点指针。
- 所有叶子节点在同一层。
- 节点的键按递增顺序排列。
- 每个节点至少包含t-1个键,最多包含2t-1个键(t是B树的阶数)。
B树插入操作
插入操作是B树的基本操作之一,分为两种情况:不需要分裂节点和需要分裂节点。具体步骤如下:
- 寻找插入位置:从根节点开始,递归寻找适合插入新键的位置。
- 插入新键:如果找到的节点未满,直接插入新键;如果节点已满,分裂节点后再插入。
以下是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]
插入操作详解
-
寻找插入位置:
- 从根节点开始,根据键值与节点中的键进行比较,递归向下寻找合适的插入位置。
- 如果当前节点是叶子节点,直接在节点中插入新键;如果是非叶子节点,继续递归寻找。
-
插入新键:
- 如果找到的节点未满(即节点中的键数小于
2t-1
),直接将新键插入到节点的合适位置。 - 如果找到的节点已满(即节点中的键数等于
2t-1
),首先进行节点分裂,将节点分为两个包含t-1
个键的新节点,并将中间键提升到父节点。
- 如果找到的节点未满(即节点中的键数小于
-
节点分裂:
- 分裂节点时,将满节点的中间键提升到父节点,并将节点分为两个包含
t-1
个键的新节点。 - 如果父节点也已满,需要递归向上分裂,直至根节点。如果根节点分裂,会产生新的根节点,树的高度增加。
- 分裂节点时,将满节点的中间键提升到父节点,并将节点分为两个包含
B树删除操作
删除操作是B树中较为复杂的操作,需要考虑多种情况。删除操作的基本步骤如下:
- 寻找删除位置:从根节点开始,递归寻找待删除键所在的节点。
- 删除键:根据不同情况进行处理,包括直接删除、合并节点、借用兄弟节点等。
以下是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)
删除操作详解(续)
-
寻找删除位置:
- 从根节点开始,根据键值与节点中的键进行比较,递归向下寻找待删除键所在的节点。
- 如果当前节点是叶子节点,直接在节点中删除键;如果是非叶子节点,根据不同情况处理。
-
删除键:
- 叶子节点:如果待删除键在叶子节点中,直接删除键。
- 非叶子节点:
- 如果待删除键在非叶子节点中,并且该键前的子节点的键数大于等于t,从该子节点中找到前驱键替代待删除键,并递归删除前驱键。
- 如果待删除键在非叶子节点中,并且该键后的子节点的键数大于等于t,从该子节点中找到后继键替代待删除键,并递归删除后继键。
- 如果待删除键在非叶子节点中,并且该键前后子节点的键数均小于t,将待删除键和后继子节点合并,然后递归删除合并后的节点中的待删除键。
-
节点借用:
- 向前兄弟节点借用:如果当前节点的前兄弟节点的键数大于等于t,从前兄弟节点借一个键到当前节点,并调整当前节点和前兄弟节点的键和子节点。
- 向后兄弟节点借用:如果当前节点的后兄弟节点的键数大于等于t,从后兄弟节点借一个键到当前节点,并调整当前节点和后兄弟节点的键和子节点。
-
节点合并:
- 合并兄弟节点:如果当前节点的键数小于t,并且兄弟节点的键数也小于t,将当前节点与兄弟节点合并,并调整父节点的键和子节点。
示例
为了更好地理解B树的插入与删除操作,我们通过一个具体示例来演示这些操作。
假设我们有一个阶数为3的B树,插入以下键:10, 20, 5, 6, 12, 30, 7, 17
插入操作:
-
插入10:
- B树为空,创建一个包含键10的根节点。
-
插入20:
- 将20插入根节点。
-
插入5:
- 将5插入根节点,键顺序为:5, 10, 20。
-
插入6:
- 将6插入根节点,键顺序为:5, 6, 10, 20。
-
插入12:
- 将12插入根节点,键顺序为:5, 6, 10, 12, 20。
-
插入30:
- 根节点已满,进行分裂。分裂后,根节点为10,左子节点为5, 6,右子节点为12, 20, 30。
-
插入7:
- 将7插入左子节点,键顺序为:5, 6, 7。
-
插入17:
- 将17插入右子节点,键顺序为:12, 17, 20, 30。
删除操作:
-
删除6:
- 6在叶子节点中,直接删除。
-
删除13:
- 13不存在,操作无效。
-
删除7:
- 7在叶子节点中,直接删除。
-
删除12:
- 12在非叶子节点中,替换为前驱键10,并删除键10。
-
删除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树的插入和删除操作,并能够应用到实际的开发中。