阅读量:0
B树的分裂与合并操作详解
引言
B树是一种自平衡的树数据结构,广泛应用于数据库和文件系统中,以维持有序数据并允许高效的插入、删除和搜索操作。作为一种多路搜索树,B树的每个节点可以有多个子节点和多个键值。B树的关键特性在于其通过分裂和合并操作来保持平衡。本文将详细介绍B树的分裂与合并操作,并提供具体的源码示例。
B树基础知识
在讨论B树的分裂与合并操作之前,先简要回顾一下B树的基本概念和性质。
B树的定义
B树是一种平衡的多路搜索树,满足以下性质:
- 每个节点最多有
m
个子节点(称为m阶B树)。 - 除根节点外,每个节点至少有
⌈m/2⌉
个子节点。 - 根节点至少有两个子节点(如果非叶子节点)。
- 每个节点包含
k-1
个键值和k
个子节点指针,其中⌈m/2⌉ ≤ k ≤ m
。 - 所有叶子节点都位于同一层。
B树的结构
B树节点的结构如下所示:
Node: keys: [k1, k2, ..., kn] children: [c0, c1, ..., cn] n: 当前节点中的键值数量 leaf: 是否为叶子节点
B树的分裂操作
分裂操作是B树在插入过程中用于保持平衡的关键操作。当一个节点中的键值数量达到最大值m-1
时,该节点需要分裂为两个节点,并将中间键提升到父节点中。
分裂操作的步骤
- 创建一个新的节点,作为分裂后的一半。
- 将中间键及其左侧的键值和子节点保留在原节点中。
- 将中间键右侧的键值和子节点移动到新节点中。
- 将中间键提升到父节点中。如果父节点也满了,递归执行分裂操作。
分裂操作的示例
假设我们有一个B树节点,包含以下键值:
Node: keys: [10, 20, 30, 40, 50] children: [c0, c1, c2, c3, c4, c5] n: 5 leaf: False
在插入新键值60时,节点已满,需要进行分裂操作:
- 创建一个新节点:
New Node: keys: [] children: [] n: 0 leaf: False
- 将中间键及其左侧键值保留在原节点中:
Original Node (after split): keys: [10, 20] children: [c0, c1, c2] n: 2 leaf: False
- 将中间键右侧的键值和子节点移动到新节点中:
New Node (after split): keys: [40, 50, 60] children: [c3, c4, c5, c6] n: 3 leaf: False
- 将中间键30提升到父节点中:
Parent Node (after split): keys: [30] children: [Original Node, New Node] n: 1 leaf: False
B树的合并操作
合并操作是B树在删除过程中用于保持平衡的关键操作。当一个节点中的键值数量低于最小值⌈m/2⌉-1
时,该节点需要与其相邻的兄弟节点合并,或者从父节点借一个键值。
合并操作的步骤
- 检查节点的左兄弟和右兄弟,选择一个键值数量大于最小值的兄弟节点。
- 将父节点中的键值下移到当前节点中。
- 将兄弟节点中的键值上移到父节点中。
- 如果兄弟节点的键值数量也低于最小值,合并当前节点和兄弟节点,并递归执行合并操作。
合并操作的示例
假设我们有一个B树节点,包含以下键值:
Parent Node: keys: [20, 40] children: [Node1, Node2, Node3] n: 2 leaf: False
删除节点Node2中的键值30后,节点中的键值数量低于最小值,需要进行合并操作:
- 检查左兄弟Node1和右兄弟Node3,选择一个键值数量大于最小值的兄弟节点。假设选择右兄弟Node3:
Node3: keys: [50, 60] children: [c5, c6, c7] n: 2 leaf: False
- 将父节点中的键值40下移到当前节点Node2中:
Node2 (after borrow): keys: [40] children: [c3, c4] n: 1 leaf: False
- 将右兄弟Node3中的键值50上移到父节点中:
Parent Node (after borrow): keys: [20, 50] children: [Node1, Node2, Node3] n: 2 leaf: False
如果右兄弟Node3的键值数量也低于最小值,则需要进行合并操作:
- 将当前节点Node2与右兄弟Node3合并:
Node2 (after merge): keys: [40, 50, 60] children: [c3, c4, c5, c6, c7] n: 3 leaf: False
- 将父节点中的键值50下移到当前节点Node2中,删除右兄弟Node3:
Parent Node (after merge): keys: [20] children: [Node1, Node2] n: 1 leaf: False
源码示例
以下是B树的分裂与合并操作的完整源码示例,使用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树。