盼望已久的五一终于到来了!我一直在考虑要不要利用这几天时间好好睡上一觉,习惯成自然,宅也是如此。睡觉都觉得无聊的时候,就有了写点什么的念头。也借此机会提高一下写作能力,看看什么时候能写一部长篇小说。
用Golang实现红黑树算是一次尝试,毕竟工作环境没用到,不知道以后会不会用。自己也是看着玩,开阔一下思路。从我开始看Golang的doc到写这篇文章利用的是大概2周中的业余时间,所以Golang的语法掌握的还有欠缺;很多特性,例如高并发等都还没有测试,如文中出现错误或不合理的地方,请指正。
本文应用的基本逻辑参考自wiki的红黑树,依据golang的语言特性部分结构可能稍有改动。同时这篇文章里也加入了我在实现过程中的想法和实现时可能会遇到的问题。wiki上的红黑树中文版本,不过建议直接看英文版本,不知道是翻译的原因还是版本没跟上,中文版本很多有助于理解的重要信息丢失了。而且最后附的源码我也大概看了一下,和上面解释的逻辑在细节上也稍有不同,如果看的话要注意。
由于代码比较占空间,我在文章里只说重点代码结构,完整代码在我的github上,有兴趣的可以去看一下,地址:rbtree源码
进入正题,下面我将介绍红黑树基于Golang的实现,及实现过程中我自己的理解。主要有以下内容:
1. 红黑树特点
红黑树的特点说明来自wiki,我这里简单翻译一下,括号里面的注是我自己的理解。
- 节点不是红色就是黑色
- 节点的根是黑色。这条有时会被忽略。过程中根一般由红色转为黑色,却没必要由黑色转为红色,这条规则会在分析时有一定影响(注:没明白这影响指什么……)
- 所有的空叶子节点都是黑色的(注:注意是空叶子节点,这个特性在插入里面用不到;在节点删除里面起作用,有点小坑,讲删除时会提到)
- 如果一个节点是红色,那么它的两个子节点都是黑色(注:也就是说树里面不会出父子节点都是红色的情况)
- 从任意一个节点到这个节点后代的每一个空叶子节点的直接路径都要经过相同个数的黑色节点。这里有几个定义:从根节点到某个节点所经过的黑色节点的数目成为这个节点的 black depth;从根节点到叶子节点的所经过的黑色节点数目成为这个树的 black-height
black-height可以用来计算时间复杂度,通过性质4、5可以计算出红黑树查询的时间复杂度为O( log2n ),这里不细证明。
再补充一点,每次插入的节点初始时都是红色,这个在添加的时候会说到,提前说一下有助于理解。
后两条是重点,当然,从这两条也能看出红黑树并不是一个完全的平衡二叉树,它的平衡在于黑色节点的平衡,上图(注:每个叶子节点都有2个空的叶子节点,文章中所有红黑树的图里面不是必要时我就不画了,显得太乱):
![红黑树示例 红黑树示例](https://img-my.csdn.net/uploads/201605/02/1462188819_7271.jpg)
红黑树的特征就是这些,下面我们将按照这些特征利用Golang建树
2. 红黑树结构的建立
2.1 节点结构
节点有红黑两色,我们先定义2个常量。布尔型足够:
const (
// RED 红树设为true
RED bool = true
// BLACK 黑树设为false
BLACK bool = false
)
然后是节点结构,包括树共有的特性:节点的值,指向父节点、左右儿子节点的3个指针;还有红黑树特有的:颜色。下面是树的结构:
// RBNode 红黑树
type RBNode struct {
value int64
color bool
left, right, parent *RBNode
}
还有一些查找父节点,兄弟节点的方法,很简单,就不细说了。
// getGrandParent() 获取父级节点的父级节点
func (rbnode *RBNode) getGrandParent() *RBNode {
'代码略……'}
// getSibling() 获取兄弟节点
func (rbnode *RBNode) getSibling() *RBNode {
'代码略……'}
// GetUncle() 父节点的兄弟节点
func (rbnode *RBNode) getUncle() *RBNode {
'代码略……'}
2.2 树的结构
树的结构只包含一个根节点Root,还有很多方法,等介绍插入删除时再细说,代码结构如下:
type RBTree struct {
root *RBNode
}
2.3 树的旋转
节点的结构里面有个方法很重要,是整个红黑树的一个核心功能,那就是树的旋转。添加和删除过程中都多次应用到了树的旋转。下面就讲一下旋转的细节。对旋