GOLANG 手撕红黑树
前言
GOLANG表中包中自带的数据结构比较少,目前找到的红黑树都是开源实现,索性自己手撕一颗来练练手。以下主要讲解代码实现。
github地址:https://github.com/Julius-Li/daily/tree/master/rbtree
红黑树简单介绍
红黑树是一种不严格平衡二叉树,通过**保持其性质**就能保持整个树的平衡。红黑树的允许左右节点之差大于1。
红黑树的性质如下:
- 每一个结点要么是红色,要么是黑色。
- 根结点是黑色的。
- 所有叶子结点都是黑色的(NIL)。叶子结点不包含任何关键字信息,所有查询关键字都在非终结点上。
- 每个红色结点的两个子节点必须是黑色的。换句话说:从每个叶子到根的所有路径上不能有两个连续的红色结点
- 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点
属性1将红黑树的所有节点上色。属性通过属性5和属性4保证了红黑树从一个节点到叶子节点最长路径(红黑交替)不大于最短路径(全黑),属性2和3将红黑树头和尾颜色限定,处理nil的同时限定了红色数量。
红黑树再具体实现的时候,查找方法和基本的二叉树没有区别,重点和难点就是在于**通过左旋右旋和置换颜色**来保持性质,从而实现平衡。
不熟悉红黑树性质和原理的同学可以参考下面文档,图文并茂: http://www.360doc.com/content/18/0904/19/25944647_783893127.shtml
红黑树的实现
再代码实现上,查找会直接贴出,重要描述在操作比较复杂的插入和删除。代码写得时候没装输入法,注释有点英文
首先定义红黑树的结构体,这里定义了两个结构体,RBTree和node。RBTree为可以直接使用的数据结构,里面存储的根节点,node是红黑树的节点存储的属性为颜色(color)、leftNode(左节点)、rightNode(右节点)、fatherNode(父节点),以及value(存储的数据)
代码如下:
//RBTree 红黑树本身为一个结构体,可以直接使用
type RBTree struct {
root *node
}
//node 节点为一个结构体
type node struct {
color bool
leftNode *node
rightNode *node
fatherNode *node
value int
}
//红黑通过bool类型来存储,并设置常量
const (
RBTRed = false
RBTBlack = true
)
//find 查找
func (t *RBTree) find(v int) *node {
n := t.root
for n != nil {
if v < n.value {
//小于当前节点的话,往左节点找
n = n.leftNode
} else if v > n.value {
//大于当前节点的话,往右节点找
n = n.rightNode
} else {
//等于的话表示找到,返回
return n
}
}
//循环结束没找到,返回
return nil
}
定义了结构体和查找方法之后我们来实现插入,在插入操作上采用二叉树的插入,插入了之后再对结构进行修正。插入的新节点应该对当前红黑树的结构进行最小的破坏(最好修正)。插入的时候新的节点颜色设置为红节点,如果插入的是黑节点,则根节点到新插入节点的黑路径比其他所有叶子节点的黑路径都大1,问题扩散开来。如果插入的是红节点,那么被破坏的性质只剩下性质2和性质4。性质2被破坏可以直接设置根节点颜色,性质4被破坏可以通过旋转变色(将两个连续的红色变成一红一黑)来解决。
func (t *RBTree) insert(v int) {
//如果根节点为nil,则先插入新的根节点。
if t.root == nil {
t.root = &node{value: v, color: RBTBlack}
return
}
n := t.root
//新插入的节点为红色
insertNode := &node{value: v, color: RBTRed}
//标记父节点
var nf *node
//一下代码找到插入位置的父节点
for n != nil {
nf = n
if v &