红黑树是实际应用中最常用的平衡二叉查找树,它不严格的具有平衡属性。(平衡属性:任意节点的左右子树的高度相差不大于1),平均的使用性能却很良好。
一、什么是红黑树。
- 结点被标记为红色和黑色两种颜色
- 根节点是黑色的,每个叶结点都是不存储数据的黑色空结点。
- 任何相邻的节点都不能同时为红色
- 任意节点到其可到达的叶结点间包含相同数量的黑色结点。
这样保证了:
- 没有一条路径比其他路径长出2倍
- 树的高度稳定趋近于 log 2 n \log_{2}{n} log2n,从而各种操作的时间复杂度为 log 2 n \log_{2}{n} log2n
- 一棵有n个结点的红黑树的高度,最多是 2 log 2 ( n + 1 ) 2\log_{2}{(n+1)} 2log2(n+1)
二、各种操作的具体实现
1.变色和旋转
(1)变色:颜色由红变黑或者由黑变红。
(2)左旋:结点的右孩子成为结点的父亲;原来结点右孩子的左子树变为,结点的右子树;结点的父结点,变成右子树的父节点
(3)右旋:与左旋对称,结点的左孩子成为结点的父结点,结点的做孩子的右子树指向结点自己,结点的父结点,变成做孩子的父结点。
注意:
(1)旋转操作不会改变树中序遍历的顺序
(2)旋转操作通过降级高度高的子树的高度,增加高度低的子树的高度来维护二叉树的平衡。
代码实现
package tree
import "fmt"
const ColorRed = 0
const ColorBlack = 1
type RBTree struct {
Root *RBNode
}
type RBNode struct {
Left, Right, Parent *RBNode
Color int
Data int
}
func (n *RBNode) LeftRotate(t *RBTree) {
if n == nil {
return
}
right := n.Right
n.Right = right.Left
if right.Left != nil {
n.Right.Parent = n
}
right.Parent = n.Parent
if n.Parent == nil {
t.Root = right
} else if n.Parent.Left == n {
n.Parent.Left = right
} else {
n.Parent.Right = right
}
right.Left = n
n.Parent = right
}
func (x *RBNode) RightRotate(t *RBTree) {
if x == nil {
return
}
y := x.Left
x.Left = y.Right
if y.Right != nil {
y.Right.Right = x
}
y.Parent = x.Parent
if x.Parent == nil {
t.Root = y
} else if x == x.Parent.Left {
x.Parent.Left = y
} else {
x.Parent.Right = y
}
y.Right = x
x.Parent = y
}
func (x *RBNode) ChangeColor() {
if x.Color == ColorRed {
x.Color = ColorBlack
} else {
x.Color = ColorRed
}
}
func (x *RBNode) GetColor() int {
if x == nil {
return ColorBlack
} else {
return x.Color
}
}
2.插入操作
结点之间的关系:
(1)父结点
(2)祖父结点:父结点的父结点
(3)叔叔结点
插入过程:
(1)插入:同二叉搜索树插入,只是每次插入结点的颜色都是红色。
(2)修复(父结点为红色事才需修复):
a. z是根节点:直接上色为黑色
b. z的叔结点是红色的: 对z的祖父结点,祖父结点,和叔结点变色。
c. z的叔结点是黑色的,并且z局部呈现直线: 旋转z的祖父结点,直线朝右则向左旋转,直线朝左,则向右旋转;原来的父亲变黑和祖父变红,变成情况b,按照b处理;
d. z的叔结点是黑色的,并且z局部呈现三角形:旋转z的父结点,使z变成直线;类似情况c 进行操作。
func (t *RBTree) SearchAddNode(ele int) *RBNode {
node := RBNode{Data: ele}
traval := t.Root
if traval == nil {
t.Root = &node
return &node
}
// 1.平衡二叉树插入
for i := 0; i < 100; i++ {
if ele < traval.Data {
if traval.Left == nil {
traval.Left = &node
node.Parent = traval
break
} else {
traval = traval.Left
}
} else {
if traval.Right == nil {
traval.Right = &node
node.Parent = traval
break
} else {
traval = traval.Right
}
}
}
return &node
}
func (t *RBTree) AddNode(ele int) {
node := t.SearchAddNode(ele)
// 父元素为红色需要修复
for node.Parent.GetColor() == ColorRed {
// 获取叔结点
var uncle *RBNode
if node.Parent.Parent.Left == node.Parent {
uncle = node.Parent.Parent.Right
} else {
uncle = node.Parent.Parent.Left
}
// 1. z的叔结点是红色的
if uncle.GetColor() == ColorRed {
uncle.ChangeColor()
node.Parent.ChangeColor()
node.Parent.Parent.ChangeColor()
node = node.Parent.Parent
continue
} else {
// 2. z的叔结点是黑色的,并且z局部呈现直线:
if node.Parent.Left == node && node.Parent.Parent.Left == node.Parent {
p := node.Parent
gp := node.Parent.Parent
gp.RightRotate(t)
p.ChangeColor()
gp.ChangeColor()
node = node.Parent.Parent
continue
}
if node.Parent.Right == node && node.Parent.Parent.Right == node.Parent {
p := node.Parent
gp := node.Parent.Parent
gp.LeftRotate(t)
p.ChangeColor()
gp.ChangeColor()
node = node.Parent.Parent
continue
}
// 3. z的叔结点是黑色的,并且z局部呈现三角形
if node.Parent.Right == node && node.Parent.Parent.Left == node.Parent {
p := node.Parent
node.Parent.LeftRotate(t)
node = p
continue
}
if node.Parent.Left == node && node.Parent.Parent.Right == node.Parent {
p := node.Parent
node.Parent.RightRotate(t)
node = p
continue
}
}
return
}
t.Root.Color = ColorBlack
}
3.删除操作
这是红黑树,最后一节,也是最复杂,最重要的一节。
要保证:
-
二叉搜索树的性质成立
- 红黑树的性质成立。
所以红黑树的删除,分为2部分:(1)类似于二叉树搜索树的删除 (2) 红黑树的修复。
删除操作如下:
(1)结点删除:
a. 没有子结点:直接删除
b.有一个子节点:删除结点,并把自己结点移动到,自己的位置
c.有两个子树,把结点和结点的后继结点的值交换,删除后继结点。
后继结点:右子树的最右侧结点,后继结点是中序遍历中下一个结点,c的做法,不破坏搜索树的性质
(2)结点修复:(上面中,c会被转化成 a 或者 b 所以,结点只会有,一个子结点,或者没有子节点)
情况 1: 删除的是红色结点(
红色结点只能是叶子结点(红色结点下,只能是两个黑色结点或者没有结点),直接删除。
情况2: 删除的是黑色结点
我们设置 删除结点后替代原结点的结点设为 x ,x的兄弟结点 为 w,我们分以下4中情况讨论(下面加上x再左,w在右侧,相反的情况对称处理)
a. w是黑色,且其子结点都是黑色
A
/ \
-1 x(黑) w(黑)
/ \
C(黑) B(黑)
由于 x端子树的黑高减小了1,我们需要把 w端的黑高统一减1,步骤如下:
- w变成红色
- 把x的父结点作为x继续进行修复操作
-1 x -- 新w
/ \
A(黑) w(红)
/ \
C(黑) B(黑)
b. w是黑色,且其右子结点,为红色.
A
/ \
-1 x(黑) w(黑)
/ \
C(黑) B(红)
通过以下步骤,就能维护红黑树的性质(不用再转化成其他情况):
- w.color = x.p.color
- x.p.color = black
w.right.color = black - left-rotate(t,x.p)
w
/ \
A(黑) B(黑)
/ \
X C(黑)
c. w是黑色,其子结点左红右黑
A
/ \
-1 x(黑) w(黑)
/ \
C(红) B(黑)
- 交换w和w左结点的颜色
- w进行右旋
- w 的父结点设置为新的w 则变成了情况 b
A
/ \
-1 x(黑) w(黑)
/ \
原c左 C(红)
/ \
原c的 B(黑)
右结点
注意次处并没有交换结点,只是改变了操作的对象
d. w是红色的
A(黑)
/ \
-1 x(黑) w(红)
/ \
C B
- w和x的父结点,颜色对掉
- 对x的父结点,进行一次左旋
- 把x的新兄弟设成w
这样不破坏红黑性质,就把,d 转化成了前面 三种情况之1
C(黑)
/ \
A(红) B
/ \
-1 X(黑) W(黑)
下面是go的实现:
func (t *RBTree) AddNode(ele int) {
node := t.BSAddNode(ele)
t.AddNodeFixUp(node)
}
func (t *RBTree) DelNode(ele int) {
node := t.FindNode(ele)
if node == nil { //没找到不用删除
return
}
// 转化成删除后继结点
nextNode := node.FindNext()
nextNode.Data, node.Data = node.Data, nextNode.Data
var sNode *RBNode
if nextNode.Left != nil {
sNode = nextNode.Left
} else {
sNode = nextNode.Right
}
sNode.Parent = nextNode.Parent
//nextNode 是根结点
if nextNode.Parent == nil {
t.Root = sNode
} else if nextNode == nextNode.Parent.Left {
nextNode.Parent.Left = sNode
} else {
nextNode.Parent.Right = sNode
}
if nextNode.Color == ColorBlack { //处理删除黑色结点的情况
t.FixDel(sNode)
}
}
func (t *RBTree) FixDel(x *RBNode) {
for x != t.Root && x.Color == ColorBlack {
if x == x.Parent.Left {//x再左边
w := x.Parent.Right
if w.Color == ColorRed {
w.Color = ColorBlack
x.Parent.Color = ColorRed
x.Parent.LeftRotate(t)
w = x.Parent.Right
}
if w.Left.Color == ColorBlack && w.Right.Color == ColorBlack {
w.Color = ColorRed
x = x.Parent
} else {
if w.Right.Color == ColorBlack {
w.Left.Color = ColorBlack
w.Color = ColorRed
w.RightRotate(t)
w = x.Parent.Right
}
w.Color = x.Parent.Color
x.Parent.Color = ColorBlack
w.Right.Color = ColorBlack
x.Parent.LeftRotate(t)
x = t.Root
}
} else{
w := x.Parent.Left
if w.Color == ColorRed {
w.Color = ColorBlack
x.Parent.Color = ColorRed
x.Parent.RightRotate(t)
w = x.Parent.Right
}
if w.Left.Color == ColorBlack && w.Right.Color == ColorBlack {
w.Color = ColorRed
x = x.Parent
} else {
if w.Right.Color == ColorBlack {
w.Left.Color = ColorBlack
w.Color = ColorRed
w.LeftRotate(t)
w = x.Parent.Left
}
w.Color = x.Parent.Color
x.Parent.Color = ColorBlack
w.Right.Color = ColorBlack
x.Parent.RightRotate(t)
x = t.Root
}
}
}
}
func (t *RBTree) FindNode(ele int) *RBNode {
if t.Root == nil {
return nil
}
node := t.Root
for node != nil {
if node.Data > ele {
node = node.Left
} else if node.Data < ele {
node = node.Right
} else {
return node
}
}
return nil
}
func (n *RBNode) FindNext() *RBNode {
if n.Right == nil {
return n
}
travl := n.Right
for travl.Left != nil {
travl = travl.Left
}
return travl
}
代码示例: gitee.com/gudongkun/datestruct
代码实现:参考 https://gitee.com/hoemfei/RBTree/blob/master/RBTree.go
视频参考:
红黑树快速入门:https://www.bilibili.com/video/BV18y4y1m721
红黑树变色和旋转:https://www.bilibili.com/video/BV1aK4y1W7yj
红黑树插入:https://www.bilibili.com/video/BV1nf4y1z7nP
观看此视频时,注意,查入结点的父结点是红色时,才需要修复,黑色不需要修复。
红黑树的删除:https://www.bilibili.com/video/BV1uZ4y1P7rr
观看此视频时,注意,平衡二叉树,的第三种情况,都会被转化成第一或第二种情况,所以, 被删除结点只会有一个子结点 那就是 x