平衡二叉排序树

平衡二叉排序树

在上一节当中,我们介绍了 二叉排序树 以及它的查找和删除等操作,但是它其实是存在一定问题的,至于到底是什么问题,下面我们慢慢来看

二叉排序树存在的问题

比如我们现在有这样一个序列,5, 9, 3, 7, 1, 4, 6, 8, 2,将它转换为二叉排序树,是下面这个样子的

看上去还不错的样子,比如我们要查找 9 这个元素,两次对比后就可以得到结果,但是如果我们将序列调整为 1, 2, 3, 4, 5, 6, 7, 8, 9,那么它就成了下面这样的

可以发现,此时竟然成为了一颗右斜树,但是它的确也是一颗二叉排序树,而如果这时如果我们再去查找 9 这个元素的话,效率可想而知,所以我们可以发现,不同的二叉树,它的查找效率是不一样的,所以我们就需要想办法找到一个合适的方式,不管提供的序列是什么样子的,都可以生成一个查找效率尽量高的二叉排序树,那就是我们下面将要介绍的『平衡二叉排序树』(AVL 树)

平衡二叉排序树

由名字就可以知道,我们的平衡二叉排序树它是一颗高度平衡的二叉树,意思就是要么它是一颗空树,要么它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值『不超过一』

用官方的话来进行描述就是,我们将二叉树上的结点上的左子树的深度的值减去右子树的深度的值称为『平衡因子 BF』(BalanceFactor),平衡二叉树就是一棵二叉树上所有结点的平衡因子的绝对值小于等于 1 的树(所以它只有三个值,10-1),比如下面这个就是『平衡二叉排序树』

而下面这个就不符合条件,因为平衡二叉排序树首先要是一棵树,接着需要是一颗二叉排序树,下面这个明显不符合二叉排序树的概念

在来看下面这个例子

它其实也不是平衡二叉排序树,因为比如结点 9,它的左子树和右子树的差的绝对值已经大于 1 了,再来看最后一个图

它就是一颗平衡二叉排序树了

平衡二叉排序树的构建过程

下面我们来看平衡二叉排序树是如何一步一步构建出来的,我们以序列 3, 2, 1, 4, 5, 6, 7, 10, 9, 8 为例,它构造出来的二叉排序树是下面这样的

让我们抛开上图,一步一步从头开始进行操作,首先是根结点 3,和它的两个左子树元素 21,那么就会是下面这样的

可以发现,这个时候就已经存在问题了,因为结点 3 的平衡因子已经为 2 了(2 - 0 = 2),所以在插入结点 1 的时候我们就应该来适当的进行调整,调整的方式为『如果平衡因子是正数,且大于 1,那么对应的整颗子树就对应的向右进行旋转(顺时针)』,就变成来下面这样

下面我们在继续来进行添加,我们在依次插入结点 45,如下

此时又出现问题了,3 的平衡因子为 -2,而又是因为它的不平衡连带导致现在的根结点 2 也不平衡了,所以我们需要来对结点 3 进行处理,调整的方式为『如果平衡因子是负数,并且小于 -1,那么对应的整颗子树就对应的向左进行旋转(逆时针)』,所以就成了下面这样

下面我们继续操作,插入结点 6,变成了下面这样

这时可以发现,4,5,6 都是符合的,但是结点 2 却不符合了,所以我们此时需要调整结点 2,可以发现它的平衡因子是负数,所以左旋转,也就成了下面这样

但是此时可以发现,旋转以后作为新的根结点的 4,它有三个子结点了,这明显不符合我们二叉树的原理的,所以此时我们就需要针对结点 3 来进行处理,因为我们之前曾经提到过,一个结点的右子树的最左边的那个孩子,事实上可以放在这个结点的左子树的最右边的孩子位置,所以就变成了下面这样,此时又变成了平衡二叉树

下面继续插入结点 7,如下

老规矩,发现结点 5 的平衡因子为 -2,所以左转,就变成了下面这样

继续插入结点 109

又发现问题,结点 9 的插入导致了结点 4,6,7BF 值均为 -2,但是这里注意,结点 10BF 是为 1 的,所以这时如果按照我们之前的逻辑,负数向左旋转,我们可以尝试一下来调整结点 7,结果如下

这下发现麻烦大了,结点 9 竟然成为了结点 10 的右孩子,所以我们的尝试是不对的,因为我们可以注意,在之前的情况当中的 BF 值要么全部为正,要么全部为负,而这一次的情况是有正也有负,所以我们需要针对这种特殊的情况来单独进行处理,所以这里,我们需要按照步骤来走,先来处理结点 10,因为它的 BF 为正,所以向右旋转,就变成了下面这样

此时的 BF 就全部为负了,我们在按照我们之前的逻辑来处理结点 7,也就是向左旋转,就得到了下面这样

这时发现就是平衡的了,所以可以再次插入我们最后的结点 8,如下

这时可以发现,结点 4,6BF 都为 -2,而结点 9BF 却是为 1 的,所以按照我们之前的逻辑需要先将结点 9 进行右转,然后此时的 BF 值就统一成负数了,这时在进行左转就得到了我们的最终结果,这里也就直接跳过了,逻辑与上方是一样的,最后可以得到下面这样的结果

平衡二叉排序树的旋转

上面看完了流程,下面我们来大致的总结一下,其实简单来说,平衡二叉树的构建过程基于二叉排序树的构建过程,不过只是在插入节点的过程中,一旦出现不平衡现象(即某节点的平衡因子大于 1 或小于 -1),就找出最小不平衡子树,进行『旋转』操作,调整最小不平衡子树中各节点的链接关系,使之成为新的平衡子树

在二叉排序树中插入节点而失去平衡的情况下,对最小不平衡子树进行调整,总共有四种『旋转』类型(LL 型,RR 型,LR 型,RL 型),分别对应不同的不平衡情况,其中 LL 型和 RR 型只需要一次旋转,而 LR 型和 RL 型则需要两次旋转,每种类型又可进一步细分为三种情况,总共 4 × 3 = 12 种情况,具体如下图所示

LL 型

最小不平衡子树的根结点平衡因子值大于 1 且与根结点左孩子节点的平衡因子符号相同

RR 型

最小不平衡子树的根结点平衡因子值小于 -1 且与根结点右孩子节点的平衡因子符号相同

LR 型

最小不平衡子树的根结点平衡因子值大于 1 且与根结点左孩子节点的平衡因子符号不相同

RL 型

最小不平衡子树的根结点平衡因子值小于 -1 且与根结点右孩子节点的平衡因子符号不相同)

代码实现

本质上与我们的排序二叉树的实现方式类似,只不过多了一个平衡因子的参数,首先建立二叉树节点进行定义

1
2
3
4
5
6
constructor(num) {
this.value = num // 存储数据
this.height = 1 // 平衡因子(多了一个这个参数)
this.leftChild = null // 左孩子节点
this.rightChild = null // 右孩子节点
}

下面是整合后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
class AVLTree {

constructor(num) {
this.value = num // 存储数据
this.height = 1 // 平衡因子(多了一个这个参数)
this.leftChild = null // 左孩子节点
this.rightChild = null // 右孩子节点
}

addNum(num) {
let result = this
// 创建节点 每一个节点都是一颗树
let point = new AVLTree(num)
if (point.value > this.value) {
if (this.rightChild == null) {
this.rightChild = point
} else {
result.rightChild = this.rightChild.addNum(num)
}
// 该树下面的左右节点平衡值
let balanceFactor = this.getBalance(this.rightChild, this.leftChild)
//差值过大 不平衡
if (balanceFactor == 2) {
if (point.value > this.rightChild.value) {
result = this.RR()
} else {
result = this.RL()
}
}
} else if (point.value < this.value) {
if (this.leftChild == null) {
this.leftChild = point
} else {
result.leftChild = this.leftChild.addNum(num)
}
let balanceFactor = this.getBalance(this.leftChild, this.rightChild)
if (balanceFactor == 2) {
if (point.value < this.leftChild.value) {
result = this.LL()
} else {
result = this.LR()
}
}
} else {
throw '输入了重复的值'
}
this.height = this.getMax(this.leftChild, this.rightChild) + 1
return result
}

deleteNum(num) {
var result = this
if (num > this.value) {
result.rightChild = this.rightChild.deleteNum(num)
} else if (num < this.value) {
result.leftChild = this.leftChild.deleteNum(num)
} else {
if (num !== this.value) {
throw '输入了没有的值'
}
if (result.leftChild !== null) {
let current = result.leftChild
// 不停的找到左节点的最右子节点
while (true) {
if (current.rightChild) {
current = current.rightChild
} else {
break
}
}
result.value = current.value
result.leftChild = result.leftChild.deleteNum(current.value)
} else if (result.rightChild !== null) {
let current = result.rightChild
while (true) {
if (current.leftChild) {
current = current.leftChild
} else {
break
}
}
result.value = current.value
result.rightChild = result.rightChild.deleteNum(current.value)
} else {
// 最后子叶节点
console.log('delete' + result)
return null
}
}
// 再平衡
if (result.getBalance(result.leftChild, result.rightChild) == 2) {
if (result.getHeight(result.leftChild.rightChild) - result.getHeight(result.leftChild.leftChild) == 1) {
result = result.LR()
} else {
result = result.LL()
}
} else if (result.getBalance(result.leftChild, result.rightChild) == -2) {
if (result.getHeight(result.rightChild.leftChild) - result.getHeight(result.rightChild.rightChild) == 1) {
result = result.RL()
} else {
result = result.RR()
}
} else {
console.log('is balance')
}
result.height = this.getMax(this.leftChild, this.rightChild) + 1
return result
}

getMax(a, b) {
let aHeight, bHeight
if (!a) {
aHeight = 0
} else {
aHeight = a.height
}
if (!b) {
bHeight = 0
} else {
bHeight = b.height
}
return aHeight > bHeight ? aHeight : bHeight
}

getBalance(a, b) {
let aValue, bValue
if (!a) {
aValue = 0
} else {
aValue = a.height
}
if (!b) {
bValue = 0
} else {
bValue = b.height
}
return aValue - bValue
}

getHeight(a) {
if (a) {
return a.height
}
return 0
}

RR() {
let a = this
let b = this.rightChild
a.rightChild = b.leftChild
b.leftChild = a
a.height = a.getMax(a.rightChild, a.leftChild) + 1
b.height = b.getMax(b.rightChild, b.rightChild) + 1
return b
}

RL() {
let a = this
a.rightChild = a.rightChild.LL()
a = a.RR()
return a
}

LL() {
let a = this
let b = this.leftChild
a.leftChild = b.rightChild
b.rightChild = a
a.height = a.getMax(a.rightChild, a.leftChild) + 1
b.height = b.getMax(b.rightChild, b.rightChild) + 1
return b
}

LR() {
let a = this
a.leftChild = a.leftChild.RR()
a = a.LL()
return a
}
}

function createTree(arr) {
let result
arr.forEach((child, index) => {
if (index === 0) {
result = new AVLTree(child)
} else {
result = result.addNum(child)
}
})
return result
}

var arr = [1, 3, 5, 7, 15, 24, 56, 11, 33, 42, 2, 4, 6, 12]
var tree = createTree(arr)
# Essay

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×