【Golang 数据结构与法算】 红黑树
package tree
import (
"data-structures-and-algorithms/contract"
"data-structures-and-algorithms/queue"
"fmt"
"strings"
)
// RedBlack 红黑树(4路平衡搜索树)
// 红黑树约束(规定了红黑树的渐进平衡性):
// (1) 树根始终为黑色
// (2) 外部节点均为黑色
// (3) 其余节点若为红色,则其孩子节点必为黑色
// (4) 从任一外部节点到根节点的沿途,黑节点的数目相等
type RedBlack struct {
Bst
}
// NewRbTree 新建空RedBlack树
func NewRbTree(cmps ...contract.Comparator) *RedBlack {
return &RedBlack{Bst: *NewBst(cmps...)}
}
// 双红修正:因新节点 x 的引入,而导致父子节点同为红色的此类情况,称作“双红”(double red)。
// 算法:
// 1.将x的父亲与祖父分别记作p和g。既然此前的红黑树合法,故作为红节点p的父亲,g必然存在且为黑色。g作为内部节点,其另一孩子(即p的兄弟、x的叔父)
// 也必然存在,将其记作u。 以下,视节点u的颜色不同,分两类情况分别处置。
// 2.1 考查u为黑色的情况。此时,x的兄弟、两个孩子的黑高度,均与u相等。从B-树角度等效地看,即同一节点不应包含紧邻
// 的红色关键码,只需令黑色关键码与紧邻的红色关键码互换颜色(伴随着1至2次结构的调整)。如此调整之后,局部子树的黑高度将复原,
// 这意味着全树的平衡也必然得以恢复。 同时,新子树的根节点b为黑色,也不致引发新的双红现象。至此,整个插入操作遂告完成。
// 2.2 节点u为红色的情况。此时,u的左、右孩子非空且均为黑色,其黑高度必与x的兄弟以及两个孩子相等。从B-树角度等效地看,即该节点因超过4度而发生上溢。
// 只需将B树中g相邻红色节点转为黑色,黑节点 g 转为红色(从B-树的角度来看,等效于上溢节点的一次分裂)。如此调整之后局部子树的黑高度复原。然而,
// 子树根节点g转为红色之后,有可能在更高层再次引发双红现象。从B-树的角度来看,对应于在关键码g被移出并归入上层节点之后,
// 进而导致上层节点的上溢,即上溢的向上传播。
// 总结:
// 2.1:2次颜色翻转,2次黑高度更新,1~2次旋转,不在递归。
// 2.2:3次颜色翻转,3次黑高度更新,0次旋转,需要递归。
func (t *RedBlack) solveDoubleRed(x *BinNode) {
// 根节点,直接变黑并更新高度
if x.isRoot() {
x.color = Black
x.height++
return
}
p := x.parent // 父节点
// 双红已修正
if p.isBlack() {
return
}
g, u := p.parent, x.uncle() // 祖父,叔叔(可能为nil)节点
// 2.1 考查u为黑色的情况
if u.isBlack() {
g.color = Red
if p.isLc() && x.isRc() || p.isRc() && x.isLc() {
x.color = Black
} else {
p.color = Black
}
t.rotateAt(x)
return
}
// 2.2 节点u为红色的情况
p.color = Black
p.height++
u.color = Black
u.height++
//if !g.isRoot() {
g.color = Red
//}
t.solveDoubleRed(g)
}
// 双黑修正:因某一无红色孩子的黑节点 x 被删除,而导致的此类复杂情况,称作“双黑”(double black) 现象
// 算法:
// 1. 原黑节点x的兄弟必然非空,将其记作s;x的父亲记作p,其颜色不确定。以下视s和p颜色的不同组合,按四种情况分别处置。
// 2.1 节点s为黑且存在红子节点t:则在b树看来,s节点的删除导致下溢,且存在元素足够多的兄弟节点。故,从父节点转借兄弟节点即可。若这三个节点按中序遍
// 历次序重命名为a、b和c,则还需将 a 和c染成黑色,b则继承p此前的颜色。 从红黑树的角度来看, 上述调整过程等效于,对节点t、s和p 实施“3 + 4”重构。
// 2.2.1 节点s及其两个孩子均为黑色时,p为红色时:从B树观察,s节点的删除导致下溢,且兄弟节点也即将下溢,故可从父节点中取出节点g对两个子节点进行合并。
// 。从红黑树角度看,这一过程可等效地理解为:s和p颜色互换。 经过以上处 理,红黑树的所有条件都在此局部得以恢复。另外,由 于关键码p原为红色,
// 在关键码p所属节点中,其左或右必然还有一个黑色关键码(当然, 不可能左、右兼有)这意味着,在关键码p从其中取 出之后,不致引发新的下溢。
// 至此,红黑树条件亦必在全局得以恢复,删除操作即告完成。
// 2.2.2 节点s、s的两个孩子以及节点p均为黑色的情况:在对应的B-树中,因关键码x的删除,导致其所属节点发生下溢。将下溢
// 节点与其兄弟合并。从红黑树的角度来看,这一过程可等 效地理解为:节点s由黑转红。然而,因s和x在此之前均为黑色,故p原所
// 属的B-树节点必然仅含p这一个关键码。于是在p被借出之后,该节点必将继而发生下溢,故有待于后续 进一步修正。 从红黑树的角度来看,此时的状态可
// 等效地理解为:节点p的父节点刚被删除。
// 2.3 考虑节点s为红色的情况:作为红节点s的父亲,节点p必为黑色;同时,s的两个孩子也应均为黑色。于是从B-树的角度来看,只需,令关键码s与p互换颜色,
// 即可得到一棵与之完全等价的B-树。而从红黑树的角度来看,这一转换对应于以节点p为轴做一次旋转,并交换节点s与p的颜色。实际上,经过这一转换之后,
// 情况已经发生了微妙而本质的变化。在转换之后的红黑树中,被删除节点x(及其替代者节点r)有了一个新的兄弟s'。与此前的 兄弟s不同,s'必然是黑的!
// 这就意味着,接下来可以套用此前所介绍其它情况的处置方法,继续并最终完成双黑修正。
// 总结:
// 2.1:2次颜色翻转,2次黑高度更新,1~2次旋转,不需要递归
// 2.2.1:2次颜色翻转,2次黑高度更新,0次旋转,不需要递归
// 2.2.2:1次颜色翻转,1次黑高度更新,0次旋转,需要递归。
// 2.3:2次颜色翻转,2次黑高度更新,1次旋转,转为2.1或2.21
func (tree *RedBlack) solveDoubleBlack(x *BinNode) {
var p *BinNode
if x != nil && x.parent != nil {
p = x.parent
}
if p == nil {
p = tree.hot
}
if p == nil {
return
}
var s *BinNode
if x == p.lc {
s = p.rc
} else {
s = p.lc
}
// 2.3 节点s为红色的情况
if s.isRed() {
s.color = Black
p.color = Red
if s.isLc() {
p.rightRotate()
} else {
p.leftRotate()
}
if s.isRoot() {
tree.root = s
}
tree.solveDoubleBlack(x)
return
}
// 黑兄弟的红子节点
var t *BinNode
if s.lc.isRed() {
t = s.lc
} else if s.rc.isRed() {
t = s.rc
}
// 2.1:节点s为黑且存在红子节点t
if t != nil {
if s.isLc() && t.isRc() || s.isRc() && t.isLc() {
t.color = p.color
} else {
s.color = p.color
t.color = Black
}
p.color = Black
tree.rotateAt(t)
return
}
// 2.1:节点s为黑且不存在红子节点t
s.color = Red
s.height--
// 2.2.1 节点s及其两个孩子均为黑色时,p为红色时
if p.isRed() {
p.color = Black
return
}
// 2.2.2 黑兄弟节点s、s的两个孩子以及节点p均为黑色的情况
p.height--
tree.hot = nil
tree.solveDoubleBlack(p)
return
}
// Insert 插入 <key, value>,存在时替换
func (t *RedBlack) Insert(key interface{}, value interface{}) {
x := t.searchAt(&t.root, key)
if *x != nil {
(*x).value = value
return
}
t.size++
*x = newBinNode(key, value, t.hot, nil, nil, Red)
(*x).height = -1
t.solveDoubleRed(*x)
}
// Remove 移除存在的key
func (t *RedBlack) Remove(key interface{}) {
x := t.searchAt(&t.root, key)
if *x == nil {
return
}
t.size--
succ := t.removeAt(x)
if t.hot == nil {
t.root.color = Black
t.root.updateHeight()
return
}
if t.balanced(t.hot) {
return
}
if succ.isRed() {
succ.color = Black
succ.updateHeight()
return
}
t.solveDoubleBlack(succ)
}
// 当前节点黑高度是否平衡
func (t *RedBlack) balanced(x *BinNode) bool {
if x.lc.getHeight() != x.rc.getHeight() {
return false
}
if x.isRed() {
return x.height == x.lc.getHeight()
}
return x.height == x.lc.getHeight()+1
}
// 层序打印红黑树基础信息
func (t *RedBlack) levelInfo() string {
var info []string
if t.Empty() {
return ""
}
que := queue.New()
que.Push(t.root)
for !que.Empty() {
var tmp string
size := que.Size()
for ; size > 0; size-- {
node := que.Pop().(*BinNode)
tmp += fmt.Sprintf("{%v,%v,%v,%v}", node.key, node.value, node.color, node.getHeight())
if node.lc != nil {
que.Push(node.lc)
}
if node.rc != nil {
que.Push(node.rc)
}
}
tmp = strings.TrimRight(tmp, "\t")
info = append(info, tmp)
}
return strings.Join(info, "\n")
}