什么是红黑树?

思考并回答以下问题:

  • 红黑树是为了解决二叉查找树多次插入新节点而导致的不平衡。怎么理解?
  • 红黑树在什么情况下需要变色?什么情况下需要旋转?

二叉查找树

要学习红黑树,需要先来理解二叉查找树(Binary Search Tree)。

二叉查找树(BST)具备什么特性呢?

  • 1.子树上所有结点的值均小于或等于它的根结点的值。
  • 2.子树上所有结点的值均大于或等于它的根结点的值。
  • 3.左、右子树也分别为二叉排序树。

下图中这棵树,就是一颗典型的二叉查找树:

这样的数据结构有什么好处呢?我们来试着查找一下值为10的节点。

1.查看根节点9:

2.由于10 > 9,因此查看右孩子13:

3.由于10 < 13,因此查看左孩子11:

4.由于10 < 11,因此查看左孩子10,发现10正是要查找的节点:

这种方式正是二分查找的思想,查找所需的最大次数等同于二叉查找树的高度。

在插入节点的时候也是利用类似的方法,通过一层一层比较大小,找到新节点适合插入的位置。

二叉查找树是个强大的数据结构,但仍然有存在它的缺陷。

缺陷体现在插入新节点的时候。让我们来看看下面这种情形:

假设初始的二叉查找树只有三个节点,根节点值为9,左孩子值为8,右孩子值为12:

接下来我们依次插入如下五个节点:7,6,5,4,3。依照二叉查找树的特性,结果会变成什么样呢?

好好的二叉树变成瘸子了。这样的形态虽然也符合二叉查找树的特性,但是查找的性能大打折扣,几乎变成了线性。

如何解决二叉查找树多次插入新节点而导致的不平衡呢?我们的主角[红黑树]应运而生了。

红黑树

红黑树(Red Black Tree)是一种自平衡的二叉查找树。除了符合二叉查找树的基本特性外,它还具有下列的附加特性:

  • 1.节点是红色或黑色。
  • 2.根节点是黑色。
  • 3.每个叶子节点都是黑色的空节点(NIL节点)。
  • 4.每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
  • 5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

下图中这棵树,就是一颗典型的红黑树:

因为这些规则限制,才保证了红黑树的自平衡。红黑树从根到叶子的最长路径不会超过最短路径的2倍。

当插入或删除节点的时候,红黑权的规则有可能被打破。这时候就需要做出一些调整,来继续维持我们的规则。

什么情况下会破坏红黑树的规则,什么情况下不会破坏规则呢?我们举两个简单的例子:

1.向原红黑树插入值为14的新节点:

由于父节点15是黑色节点,因此这种情况并不会破坏红黑树的规则,无需做任何调整。

2.向原红黑树插入值为21的新节点:

由于父节点22是红色节点,因此这种情况打破了红黑树的规则4(每个红色节点的两个子节点都是黑色),必须进行调整,使之重新符合红黑树的规则。

那么,我们需要做出怎样的调整,才能保证一颗红黑树始终是红黑树呢?

调整有两种方法: [变色]和[旋转]。而旋转又分成两种形式:[左旋转]和[右旋转]。

变色

为了重新符合红黑树的规则,尝试把红色节点变为黑色,或者把黑色节点变为红色。

下图所表示的是红黑树的一部分,需要注意节点25并非根节点。因为节点21和节点22连续出现了红色,不符合规则4,所以把节点22从红色变成黑色:

但这样并不算完,因为凭空多出的黑色节点打破了规则5,所以发生连锁反应,需要继续把节点25从黑色变成红色:

此时仍然没有结束,因为节点25和节点27又形成了两个连续的红色节点,需要继续把节点27从红色变成黑色:

左旋转

逆时针旋转红黑树的两个节点,使得父节点被自己的右孩子取代,而自己成为自己的左孩子。说起来很怪异,大家看下图:

图中,身为右孩子的Y取代了X的位置,而X变成了自己的左孩子。此为左旋转。

右旋转

顺时针旋转红黑树的两个节点,使得父节点被自己的左孩子取代,而自己成为自己的右孩子。大家看下图:

图中,身为左孩子的Y取代了X的位置,而X变成了自己的右孩子。此为右旋转。

究竟什么时候用到变色,什么时候用到旋转呢?

红黑树的插入和删除包含很多种情况,每一种情况都有不同的处理方式。在这里我们举一个典型例子,大家体会一下。

我们以刚才插入节点21的情况为例:

首先,我们需要做的是变色,把节点25及其下方的节点变色:

此时节点17和节点25是连续的两个红色节点,那么把节点17变成黑色节点?恐怕不合适。这样一来不但打破了规则4,而且根据规则2(根节点是黑色),也不可能把节点13变成红色节点。

变色已无法解决问题,我们把节点13看做X,把节点17看做Y,像刚才的示意图那样进行左旋转

由于根节点必须是黑色节点,所以需要变色,变色结果如下:

这样就结束了吗?并没有。因为其中两条路径(17 -> 8 -> 6 -> NIL)的黑色节点个数是4,其他路径的黑色节点个数是3,不符合规则5。

这时候我们需要把节点13看做X,节点8看做Y,像刚才的示意图那样进行右旋转

最后根据规则来进行变色

如此一来,我们的红黑树变得重新符合规则。这一个例子的调整过程比较复杂,经历了如下步骤:

变色 -> 左旋转 -> 变色 -> 右旋转 -> 变色

红黑树的应用有很多,其中JDK的集合类TreeMap和TreeSet底层就是红黑树实现的。在Java8中,连HashMap也用到了红黑树。

几点说明

1.关于红黑树自平衡的调整,插入和删除节点的时候都涉及到很多种Case,由于篇幅原因无法展开来一一列举,有兴趣的朋友可以参考维基百科,里面讲的非常清晰。

2.漫画中红黑树调整过程的示例是一种比较复杂的情形,没太看明白的小伙伴也不必钻牛角尖,关键要懂得红黑树自平衡调整的主体思想。

PHP实现红黑树

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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
<?php
/**
* 红黑树
* 1.节点是红色或黑色
* 2.根是黑色
* 3.所有的叶子节点都是黑色
* 4.每个红色节点必须有两个黑色节点
* 5.从任一节点到其叶子节点的简单路径都包含相同数量的黑色节点
*
*/
class Node
{
private $id;

/**
* @var
* 1 => red
* 2 => black
*/
private $color = 1;

private $value = null;

public $parent = 0;

public $left = 0;

public $right = 0;

public function __construct($value)
{
$this->id = uniqid();
$this->value = $value;
}

public function getID()
{
return $this->id;
}

public function getValue()
{
return $this->value;
}

public function setColor($color)
{
$this->color = $color;
}

public function getColor()
{
return $this->color;
}
}

class RedBlackTree
{
public $root = 0;

private $nodePool = [];

private $nil;

public function __construct()
{
$nilNode = new Node(null);
$nilNode->setColor(2);
$this->addNodeToPool($nilNode->getID(), $nilNode);
$this->nil = $nilNode->getID();
$this->root = $this->nil;
}

public function addNodeToPool($id, $object)
{
$this->nodePool[$id] = $object;
}

/**
* @param $id
* @return Node
*/
public function getNodeFromPool($id)
{
return isset($this->nodePool[$id]) ? $this->nodePool[$id] : null;
}

public function find($value)
{
$tmp = $this->getNodeFromPool($this->root);

while (!is_null($tmp->getValue()))
{
if ($tmp->getValue() > $value)
{
$tmp = $this->getNodeFromPool($tmp->left);
} else if ($tmp->getValue() < $value)
{
$tmp = $this->getNodeFromPool($tmp->right);
} else
{
return true;
}
}

return false;
}

public function walk($nodeID)
{
var_dump($this->root);
var_dump($this->nodePool);
}

public function insert($value)
{
$tmpNode = $this->getNodeFromPool($this->root);
$prevNode = $tmpNode;

while (!is_null($tmpNode->getValue()))
{
$prevNode = $tmpNode;

if ($tmpNode->getValue() > $value)
{
$tmpNode = $this->getNodeFromPool($tmpNode->left);
} else
{
$tmpNode = $this->getNodeFromPool($tmpNode->right);
}
}

$newNode = new Node($value);
$newNode->left = $this->nil;
$newNode->right = $this->nil;
$newNode->parent = $prevNode->getID();

$this->addNodeToPool($newNode->getID(), $newNode);

if (is_null($prevNode->getValue()))
{
//第一个节点
$newNode->setColor(2);
$this->root = $newNode->getID();
}
else if ($prevNode->getValue() > $newNode->getValue())
{
$prevNode->left = $newNode->getID();
}
else
{
$prevNode->right = $newNode->getID();
}

$this->insertFix($newNode->getID());

return true;
}

protected function insertFix($node)
{
$tmpNode = $this->getNodeFromPool($node);

while (!is_null($tmpNode->getValue()) && ($this->getNodeFromPool($tmpNode->parent)->getColor() == 1))
{
$grandP = $this->getNodeFromPool($this->getNodeFromPool($tmpNode->parent)->parent);
$parent = $this->getNodeFromPool($tmpNode->parent);

if ($tmpNode->parent == $grandP->left)
{
$rightUncle = $this->getNodeFromPool($grandP->right);

if ($rightUncle->getColor() == 1)
{
$parent->setColor(2);
$rightUncle->setColor(2);
$grandP->setColor(1);

$tmpNode = $grandP;
}
else if ($tmpNode->getID() == $parent->right)
{
$this->leftRotate($tmpNode->getID());

$tmpNode = $parent;

$this->rightRotate($tmpNode->parent, true);
}
else
{
$this->rightRotate($tmpNode->getID(), true);
}


}
else
{
$leftUncle = $this->getNodeFromPool($grandP->left);

if ($leftUncle->getColor() == 1)
{
$parent->setColor(2);
$leftUncle->setColor(2);
$grandP->setColor(1);

$tmpNode = $grandP;
}
else if ($tmpNode->getID() == $parent->left)
{

$this->rightRotate($tmpNode->getID());

$tmpNode = $parent;
$this->leftRotate($tmpNode->parent, true);
}
else
{
$this->leftRotate($tmpNode->getID(), true);
}
}
$this->getNodeFromPool($this->root)->setColor(2);
}
}

protected function leftRotate($nodeID, $changeColor = false)
{
$node = $this->getNodeFromPool($nodeID);
$parentID = $node->parent;

$parent = $this->getNodeFromPool($parentID);

$grandPID = $parent->parent;

$grandP = $this->getNodeFromPool($grandPID);

if (is_null($grandP->getValue()))
{
$this->root = $nodeID;
}
else
{
if ($grandP->left == $parentID)
{
$grandP->left = $nodeID;
}
else
{
$grandP->right = $nodeID;
}
}

if ($changeColor)
{
$node->setColor(2);
$parent->setColor(1);
}

$leftNode = $this->getNodeFromPool($node->left);

if (!is_null($leftNode->getValue()))
{
$leftNode->parent = $parentID;
}

$parent->right = $node->left;
$parent->parent = $nodeID;

$node->parent = $grandPID;
$node->left = $parentID;

}

protected function rightRotate($nodeID, $changeColor = false)
{
$node = $this->getNodeFromPool($nodeID);
$parentID = $node->parent;

$parent = $this->getNodeFromPool($parentID);

$grandPID = $parent->parent;

$grandP = $this->getNodeFromPool($grandPID);

if (is_null($grandP->getValue()))
{
$this->root = $nodeID;
}
else
{
if ($grandP->left == $parentID)
{
$grandP->left = $nodeID;
}
else
{
$grandP->right = $nodeID;
}
}

if ($changeColor)
{
$node->setColor(2);
$parent->setColor(1);
}

$rightNode = $this->getNodeFromPool($node->right);

if (!is_null($rightNode->getValue()))
{
$rightNode->parent = $parentID;
}

$parent->left = $node->right;
$parent->parent = $nodeID;

$node->parent = $grandPID;
$node->right = $parentID;
}


public function delete($value)
{
//TODO
}
}

$test = [12, 550, 300, 20, 1, 50, 44, 34, 24, 25, 79, 11, 21, 1000, 3000, 99];

$testH = [12, 550, 300, 20, 1, 50, 44, 34, 24, 25, 79, 19, 99];

echo '<pre>';
$tree = new RedBlackTree();

foreach ($test as $value)
{
$tree->insert($value);
}

foreach ($testH as $v)
{
if ($tree->find($v))
{
echo 'true;';
}
else
{
echo 'false;';
}
}
// $tree->walk($tree->root);
0%