Struct
type Tree struct {
Root *Node
Comparator utils.Comparator
size int
}
type Node struct {
Key interface{}
Value interface{}
Parent *Node
Children [2]*Node
b int8
}
这里子树,并没有用左右来表示,而是直接使用了一个数组,数组的 0 号元素相当于左树,1 号相当于右树,这和一会需要做的旋转操作也有关系。
存储的结构是 Key - Value 形式的,key 也作为树的关键字,它的比较函数存储在 Comparator
字段中,因为树的插入,删除,查找等工作都需要进行对比,使用规律非常频繁。
b 就是使这个二叉树保持平衡的关键了,按照常理来说它应该是指 avl 树的高度吧,但是高度不应该直接用 h 表示吗,为什么用了 b 这个字母,这一点还需要在代码中考察。
Floor() & Ceiling()
这里有两个比较特别的函数, Floor()
是用来寻找小于或等于给定节点的最大节点, Ceiling()
则是寻找大于或等于给定节点的最小函数。
func (t *Tree) Floor(key interface{}) (floor *Node, found bool)
func (t *Tree) Ceiling(key interface{}) (floor *Node, found bool)
最后的布尔值表示是否查找成功,不成功有两种情况,树为空,所有节点都比给定节点大(小)。
Put()
为了保证 AVL 树的左子树和右子树的最大高度差为 1,我们肯定需要监控树的高度,那么树的高度发生变化只有在插入或者删除的时候。我们先来看一下插入的函数
func (t *Tree) Put(key interface{}, value interface{}) {
t.put(key, value, nil, &t.Root)
}
func (t *Tree) put(key interface{}, value interface{}, p *Node, qp **Node)
很轻松的就可以看出,这里真正的实现函数是 put()
函数,下面是它的函数签名,那后面的 qp 是什么意思,为什么它是二重指针,p 在这里又代表了什么
这里 qp 应该是指 q 的 pointer,因为节点是使用 *Node
存放的,所以 q 的指针就应该使用二重指针了,那这里为什么必须要传指针呢,这里 p 是代表的 parent,如果我们传递的是 p 的 children 那么修改的时候并不会对 p 中的 children 产生影响,所以这里必须传递二重指针。
func (t *Tree) put(key interface{}, value interface{}, p *Node, qp **Node) bool {
q := *qp
if q == nil {
t.size++
*qp = &Node{Key: key, Value: value, Parent: p}
return true
}
c := t.Comparator(key, q.Key)
if c == 0 {
q.Key = key
q.Value = value
return false
}
if c < 0 {
c = -1
} else {
c = 1
}
a := (c + 1) / 2
var fix bool
fix = t.put(key, value, q, &q.Children[a])
if fix {
return putFix(int8(c), qp)
}
return false
}
在这段代码中我们可以看到, put()
是递归调用的,而决定是否需要修改节点的是由 fix 这个变量决定的,这里有这里给出了两种可能一种是新建节点必须检查,另一个是由 putFix()
决定的,我们再观察 putFix()
这个函数的签名, func putFix(c int8, t **Node)
,发现它不但传递了新建的节点,而且还将比较的结果也传进去了, c == 1
时,新建的节点是右子树, c == -1
时,新建节点在左子树,这就将左旋和右旋分割开来,只需要调用一个函数就可以了,大大降低了思考的成本。
func putFix(c int8, t **Node) bool {
s := *t
if s.b == 0 {
s.b = c
return true
}
if s.b == -c {
s.b = 0
return false
}
if s.Children[(c+1)/2].b == c {
s = singlerot(c, s)
} else {
s = doublerot(c, s)
}
*t = s
return false
}
首先我们按照第一次插入来看这个函数,最末端一次调用的时候,qp 是 k3,是新建的节点,那么当这个函数返回,调用 futFix()
的时候 pq 就应该是 k2 了,其实这里的 b 是记录上次插入的位置,如果我们上次对这个节点在左子树上进行插入,那么我们下次如果还在左子树上插入就不平衡了,所以 b 是为了限制不能在同一个方向上连续插入两次。
func singlerot(c int8, s *Node) *Node {
s.b = 0
s = rotate(c, s)
s.b = 0
return s
}
func doublerot(c int8, s *Node) *Node {
a := (c + 1) / 2
r := s.Children[a]
s.Children[a] = rotate(-c, s.Children[a])
p := rotate(c, s)
switch {
default:
s.b = 0
r.b = 0
case p.b == c:
s.b = -c
r.b = 0
case p.b == -c:
s.b = 0
r.b = c
}
p.b = 0
return p
}
func rotate(c int8, s *Node) *Node {
a := (c + 1) / 2
r := s.Children[a]
s.Children[a] = r.Children[a^1]
if s.Children[a] != nil {
s.Children[a].Parent = s
}
r.Children[a^1] = s
r.Parent = s.Parent
s.Parent = r
return r
}
Remove()
同样 Remove()
的底层是 remove()
,而且也是使用递归进行删除,参数的传递也是二重指针,这样才能保证影响到存储这个节点的每个位置。
我们先来看一下真正的删除代码,如果当前的 Key 和需要删除的 Key 相同,就需要对这个节点进行删除,并且找一个节点来这里填充这个节点,这个实现中选择的是右子树的最小节点,当然也可以是左子树的最大节点。
c := t.Comparator(key, q.Key)
if c == 0 {
t.size--
if q.Children[1] == nil {
if q.Children[0] != nil {
q.Children[0].Parent = q.Parent
}
*qp = q.Children[0]
return true
}
fix := removeMin(&q.Children[1], &q.Key, &q.Value)
if fix {
return removeFix(-1, qp)
}
return false
}
func removeMin(qp **Node, minKey *interface{}, minVal *interface{}) bool {
q := *qp
if q.Children[0] == nil {
*minKey = q.Key
*minVal = q.Value
if q.Children[1] != nil {
q.Children[1].Parent = q.Parent
}
*qp = q.Children[1]
return true
}
fix := removeMin(&q.Children[0], minKey, minVal)
if fix {
return removeFix(1, qp)
}
return false
}