1. 红黑树
红黑树是一种自平衡的二叉搜索树,它可以保证在最坏情况下每个基本操作的时间复杂度都是O(log n)。红黑树中的节点可以是红色或黑色,根节点为黑色,叶子节点为黑色的空节点。红黑树满足以下性质:
性质一:节点是红色或黑色。
性质二:根节点是黑色。
性质三:所有叶子节点都是黑色的空节点(即null节点)。
性质四:每个红色节点的两个子节点都是黑色的。
性质五:从任意一个节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。
1.1 红黑树的基本操作
红黑树支持基本的搜索、插入、删除操作,它们的时间复杂度都是O(log n)。
1.1.1 插入操作
红黑树的插入操作需要维护红黑树的性质,插入节点时按照二叉搜索树的规则进行插入,具体流程可描述如下:
将红黑树作为一个二叉搜索树,将要插入的节点插入到二叉搜索树中。
将插入的节点标记为红色。
通过一系列的旋转和着色操作,调整红黑树,使其重新满足红黑树的五个性质。
下面是红黑树的插入操作的Go语言代码实现:
type node struct {
key int
value string
isBlack bool
left, right *node
}
func (n *node) insert(key int, value string) *node {
if n == nil {
return &node{key: key, value: value, isBlack: false}
}
if key < n.key {
n.left = n.left.insert(key, value)
} else if key > n.key {
n.right = n.right.insert(key, value)
} else {
n.value = value
}
if !n.left.isBlack && !n.right.isBlack {
n.flipColors()
}
if n.right.isBlack && !n.left.isBlack {
n = n.rotateLeft()
}
if !n.right.isBlack && n.right.right.isBlack {
n = n.rotateRight()
}
if !n.right.isBlack && !n.left.isBlack {
n.flipColors()
}
return n
}
1.1.2 删除操作
红黑树的删除操作也需要维护红黑树的性质,在删除节点时需要考虑多种情况,具体流程可描述如下:
如果要删除的节点是叶子节点,则直接删除它。
如果要删除的节点只有一个子节点,则用它的子节点替代该节点。
如果要删除的节点有两个子节点,则在其右子树中找到最小的节点来替代该节点,并删除该节点。
通过一系列的旋转和着色操作,调整红黑树,使其重新满足红黑树的五个性质。
下面是红黑树的删除操作的Go语言代码实现:
func (n *node) delete(key int) *node {
if key < n.key {
if n.left == nil {
return n
}
if n.left.isBlack && n.left.left.isBlack {
n = n.moveRedLeft()
}
n.left = n.left.delete(key)
} else {
if n.left.isRed {
n = n.rotateRight()
}
if key == n.key && n.right == nil {
return nil
}
if n.right.isBlack && n.right.left.isBlack {
n = n.moveRedRight()
}
if key == n.key {
min := n.right.getMin()
n.key, n.value = min.key, min.value
n.right = n.right.deleteMin()
} else {
n.right = n.right.delete(key)
}
}
return n.fixUp()
}
2. B Tree
B Tree是一种多路搜索树,可以用来存储大量的数据,它与红黑树类似,但更适合在磁盘或其他外部存储设备上使用。B Tree的每个节点可以有多个子节点。
B Tree具有以下性质:
性质一:所有叶子节点都在同一层。
性质二:每个节点最多有m个子节点,其中m是树的阶数。
性质三:除了根节点和叶子节点外,每个节点至少有ceil(m/2)个子节点,其中ceil是向上取整函数。
2.1 B Tree的基本操作
B Tree的基本操作包括搜索、插入、删除等,这些操作都需要维护B Tree的性质。
2.1.1 插入操作
B Tree的插入操作需要维护B Tree的平衡性,具体流程可描述如下:
在B Tree中搜索插入节点的位置,如果插入节点已经存在,则直接返回。
如果叶子节点未满,则将节点插入叶子节点中。
如果叶子节点已满,则将节点插入到叶子节点中,并将该节点分裂成两个节点。
将分裂出来的节点插入到父节点中。
如果父节点已满,则对父节点进行分裂操作,并递归向上传递分裂操作。
下面是B Tree的插入操作的Go语言代码实现:
type node struct {
keys []int
values []string
leaf bool
parent *node
child []*node
}
func (n *node) insert(key int, value string) {
if n.leaf {
n.insertNonFull(key, value)
return
}
i := 0
for i < len(n.keys) && key > n.keys[i] {
i++
}
if n.child[i].isFull() {
n.splitChild(i)
if key > n.keys[i] {
i++
}
}
n.child[i].insert(key, value)
}
2.1.2 删除操作
B Tree的删除操作也需要维护B Tree的平衡性,它与插入操作类似,具体流程可描述如下:
在B Tree中搜索要删除的节点。
如果要删除的节点是叶子节点,则直接删除该节点。
如果要删除的节点不是叶子节点,则找到该节点的后继节点,用后继节点替换该节点。
递归删除后继节点。
删除节点后,需要进行合并操作,以保证B Tree的平衡性。
下面是B Tree的删除操作的Go语言代码实现:
func (n *node) delete(key int) {
i := 0
for i < len(n.keys) && key > n.keys[i] {
i++
}
if i < len(n.keys) && key == n.keys[i] {
if n.leaf {
n.removeLeaf(i)
} else {
n.removeInternal(i)
}
} else {
if n.leaf {
return
}
if n.child[i].isMinimal() {
n.fixChild(i)
}
n.child[i].delete(key)
}
}
func (n *node) removeLeaf(i int) {
n.keys = append(n.keys[:i], n.keys[i+1:]...)
n.values = append(n.values[:i], n.values[i+1:]...)
}
func (n *node) removeInternal(i int) {
if n.child[i].isMinimal() {
n.leftShift(i)
}
pred := n.child[i].findPred(i)
n.keys[i] = pred.keys[len(pred.keys)-1]
n.values[i] = pred.values[len(pred.values)-1]
pred.removeLeaf(len(pred.keys) - 1)
}
3. B+ Tree
B+ Tree是一种变体的B Tree,它与B Tree类似,但有些性质不同。在B+ Tree中,每个非叶子节点都只包含键值的索引,而不包含实际数据,所有的数据都保存在叶子节点中。B+ Tree的叶子节点形成了一颗链表,使得范围查询操作更加高效。
B+ Tree具有以下性质:
性质一:所有叶子节点都在同一层,并形成一条链表。
性质二:每个非叶子节点最多有m个子节点,其中m是树的阶数。
性质三:除了根节点和叶子节点外,每个非叶子节点至少有ceil(m/2)个子节点。
3.1 B+ Tree的基本操作
B+ Tree的基本操作与B Tree类似,只是需要特殊处理叶子节点以及范围查询操作。
3.1.1 插入操作
B+ Tree的插入操作也类似于B Tree,只是需要特殊处理叶子节点,具体流程可描述如下:
在B+ Tree中搜索插入节点的位置,如果插入节点已经存在,则直接返回。
如果叶子节点未满,则将节点插入叶子节点中。
如果叶子节点已满,则将节点插入到叶子节点中,并将该节点分裂成两个节点。
将分裂出来的节点插入到父节点中,并更新父节点的索引。
如果父节点已满,则对父节点进行分裂操作,并递归向上传递分裂操作。
下面是B+ Tree的插入操作的Go语言代码实现:
type node struct {
keys []int
parent *node
children []*node
leaf bool
prev, next *node
}
type leafNode struct {
keys []int
values []string
parent *node
next *leafNode
}
func (n *node) insert(key int, value string) {
if n.leaf {
n.insertLeaf(key, value)
return
}
i := n.searchInsertIndex(key)
if n.children[i].isFull() {
n.splitChild(i)
if key > n.keys[i] {
i++
}
}
n.children[i].insert(key, value)
}
func (ln *leafNode) insert(key int, value string) {
i := ln.searchInsertIndex(key)
if i < len(ln.keys) && ln.keys[i] == key {
ln.values[i] = value
} else {
ln.keys = append(ln.keys, 0)
ln.values = append(ln.values, "")
copy(ln.keys[i+1:], ln.keys[i:])
copy(ln.values[i+1:], ln.values[i:])
ln.keys[i] = key
ln.values[i] = value
}
if len(ln.keys) > maxKeys {
ln.split()
}
}
3.1.2 删除操作
B+ Tree的删除操作也类似于B Tree,只是需要特殊处理叶子节点,并通过更新父节点的索引来保持B+ Tree的平衡性,具体流程可描述如下:
在B+ Tree中搜索要删除的节点。
如果要删除的节点是叶子节点,则直接删除该节点。
如果要删除的节点不是叶子节点,则找到该节点的后继节点,用后继节点替换该节点,并更新父节点的索引。
递归删除后继节点。
删除节点后,需要进行合并操作,以保证B+ Tree的平衡性。
下面是B+ Tree的删除操作的Go语言代码实现:
func (n *node) delete(key int) {
if n.leaf {
ln := n.getLeaf()
ln.delete(key)
if len(ln.keys) < minKeys && ln.parent != nil {
ln.parent.fixUnderflow(ln)
}
return
}
i := n.searchNextIndex(key)
if n.children[i].isMinimal() {
n.fixChild(i)
}
n.children[i].delete(key)
}
func (ln *leafNode) delete(key int) {
i := ln.searchExactIndex(key)
if i < len(ln.keys) && ln.keys[i] == key {
copy(ln.keys[i:], ln.keys[i+1:])
copy(ln.values[i:], ln.values[i+1:])
ln.keys = ln.keys[:len(ln.keys)-1]
ln.values = ln.values[:len(ln.values)-1]
}
}