简介
红黑树是二叉搜索树中的一种,可以保证在最坏情况下基本动态集合操作(SEARCH、PREDECESSOR、SUCCESSOR、MINIMUM、MAXIMUM、INSERT、INSERT)的时间复杂度为O(log n)。
红黑树是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
红黑树的应用比较广泛,主要是用它来存储有序的数据,例如,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。
性质
红黑树首先是一种二叉搜索树,所以首先满足以下条件:
设x是二叉搜索树中的一个节点。如果y是x左子树中的一个节点,那么y.key小于或等于x.key;如果y是右子树种的一个字节,那么y.key大于或等于x.key。
但是,红黑树有自己的特性(一定要抓住这个特性进行各项分析):
1.每个节点或者是红色的,或者是黑色的。
2.根节点是黑色的。
3.每个叶子节点(NIL)是黑色。
4.如果一个节点是红色的,则它的两个子节点都是黑色的。
5.对每个节点,从该节点到其所有后代叶节点的简单路径上均包含相同数目的黑色节点。
对任意结点,从该结点的到其后代叶结点的最长的可能路径不多于最短的可能路径的2倍。 证明:最长的路径由红、黑结点相间组成,最短的路径全都由黑色结点组成,由于黑色结点数目相同,那么最长路径不会超过最短路径两倍。
定理:一棵含有n个内部节点的红黑树的高度至多为2log(n+1).
证明:
先证明以任一节点x为根的子树中至少包含2bh(x)-1。要证明这点,对x的高度进行归纳。如果x的高度为0,则x必为叶节点(T.nil),且以x为根节点的子树至少包含2bh(x)-1 = 2bh(0)-1 = 0 个内部节点。对于归纳步骤,考虑一个高度为正值且有两个子节点的内部节点x。每个子节点有黑高bh(x)或bh(x)-1,其分别取决于自身的颜色是红还是黑。由于x子节点的高度比x本身要低,可以利用归纳假设得出每个子节点至少有2bh(x)-1-1个内部节点的结论。于是,以x为根的子树至少包含(2bh(x)-1-1)+(2bh(x)-1-1)+1 = 2bh(x)-1个内部节点,因此得证。
为完成定理的证明,设h为树的高度。根据性质4,从根节点到叶子节点(不包括根节点)的任何一条简单路径上都至少有一半的节点为黑色。因此,根的黑高至少为h/2;于是有
n >= 2h/2-1
把1移到不等式的左边,再对两边取对数,得到lg(n+1) >= h/2,或者h <= 2lg(n+1) 。
旋转
对红黑树进行插入和删除操作后,结果可能违反红黑树的性质。为了维护这些性质,必须要改变树中某些节点的颜色及指针结构。指针结构的修改是通过旋转(rotation)来完成的。
假设 x.right != T.nil 且根节点的父节点为T.nil(x对应动画中的A,y对应动画中的B)。
左旋操作参考伪代码如下:
LEFT-ROTATE(T,x)
y = x.right // set y
x.right = y.left // turn y's left subtree into x's right subtree
if y.left != T.nil
y.left.p = x
y.p = x.p // link x's parent to y
if x.p == T.nil // x is root node
T.root = y
elseif x == x.p.left // x is left node
x.p.left = y
else x.p.right = y // x is right node
y.left = x // put x on y's left
x.p = y
实际的左旋操作如图:
插入
我们可以在O(lg n)时间内完成向一个含n个节点的红黑树中插入一个新的节点z,该z节点首先着为红色(因为红色不会影响路径上黑色节点的数量,保持性质4)。
插入操作比较简单,直接找到对应的位置进行插入即可,但是插入后可能破坏红黑树的性质。为保证插入后能继续保持红黑树的性质,我们需要调用一个辅助程序RB-INSERT-FIXUP(T, z)来对节点重新着色和旋转。
直接参考伪代码进行分析可以更容易地对红黑树的插入操作进行理解。
- 插入节点:
RB-INSERT(T, z)
y = T.nil
x = T.root
while x != T.nil
y = x
if z.key < x.key
x = x.left
else x = x.right
z.p = y
if y == T.nil
T.root = z
elseif z.key < y.key
y.left = z
else y.right = z
z.left = T.nil
z.right = T.nil
z.color = RED
RB-INSERT-FIXUP(T, z)
- 重新着色和旋转:
RB-INSERT-FIXUP(T, z)
while z.p.color == RED
if z.p == z.p.p.left // z.p is the left node of it's parent
y = z.p.p.right // y is z's uncle
if y.color == RED
z.p.color = BLACK // CASE 1
y.color = BLACK // CASE 1
z.p.p.color = RED // CASE 1
z = z.p.p // CASE 1
else if z == z.p.right
z = z.p // CASE 2
LEFT_ROTATE(T, z) // CASE 2
z.p.color = BLACK // CASE 3
z.p.p.color = RED // CASE 3
RIGHT_ROTATE(T, z.p.p) // CASE 3
else(same as the previous "if" clause with "right" and "left" extranged.) // z's parent is a right child
T.root.color = BLACK
上述CASE 1、CASE 2、CASE 3对应如下图所示情况1、情况2、情况3的操作,可以结合代码进行理解。
图示说明:
(a) 插入后的节点z。由于z和它的父节点z.p都是红色的,所以违反了性质4。由于z的叔节点y是红色的,可以应用程序中的情况1。节点被重新着色,并且指针z沿树上升,所得树如(b)所示。再一次z及其父节点又都为红色,但z的树节点y是黑色的。因为z是z.p的右孩子,可以应用情况2。在执行1次左旋后,所得结果树见©。现在,z是其父节点的左孩子,可以应用情况3。重新着色请执行一次右旋后的(d)中的树,它是一棵合法的红黑树。
可能需要修复的情况:
1.如果插入的结点是根结点,破坏了性质2;
2.如果插入结点的父结点为红色,则破坏了性质4。
如果破坏了性质4,会分成如下3种情况解决,假设插入的结点为z,并且z的父结点是其祖父结点的左孩子(如果是右孩子操作与左孩子相反)。
情况1(CASE 1):z的叔节点y是红色的
操作方法:父结点和叔结点着黑色,祖父结点着红色,z结点上移到祖父结点,循环继续遍历。
情况2(CASE 2):z的叔节点y是黑色的且z是一个右孩子
操作方法:z的父结点左旋变成情况3
情况3(CASE 3):z的叔节点y是黑色的且z是一个左孩子
操作方法:z的父结点着黑色,祖父结点着红色,z的祖父结点右旋,结束调整。
删除
与n个节点的红黑树的其他基本操作一样,删除一个节点要花费O(lg n)时间。与插入操作相比,删除操作要稍微复杂一些。
从一棵二叉搜索树中删除节点z参考二叉搜索树如下图所示。操作要点:找到z的后继节点进行。
图示说明:节点z可以是树根,可以是节点q的一个左孩子或右孩子。(a)节点z没有左孩子。用其右孩子r来替换z,其中r可以是nil,也可以不是。(b)节点z有一个左孩子l但没有右孩子。用l来替换z。©节点z有两个孩子,其左孩子是节点l,其右孩子y还是其后继,y的右孩子是节点x。用y替换z,修改使l成为y的左孩子,但保留x仍然为y的右孩子。(d)节点z有两个孩子(左孩子l和右孩子r),并且z的后继 y!=r 位于以r为根的子树中。用y自己的右孩子x来代替y,并且置y为r的双亲。然后(第二个箭头所示),再置y为q的孩子和l的双亲。
为了在红黑树中移动子树,首先定义一个子过程RB-TRANSPLANT,它是用另一棵子树替换一棵子树并成为其双亲的孩子节点。当RB-TRANSPLANT用 一棵以v为根的子树来替换一棵以u为根的子树时,节点u的双亲就变成了节点v的双亲,并且最后v成为u的双亲的孩子。
RB-TRANSPLANT(T,u,v)
if u.p == T.nil
T.root = v
else if u == u.p.left
u.p.left = v
else u.p.right = v
v.p = u.p
- 删除节点
要点:
- 找到替换z的结点y
- 如果z左子树为null,z的右孩子r不为null,移到z位置上,x=r,记录y.color=z.color
- 如果z右子树为null,z的左孩子l不为null,移到z位置上,x=l,记录y.color=z.color
- z左右子树均不为null,存在后继结点,y设置为z的中序遍历的后继结点,记录后继结点y的y.color,x=y.right,y替换z,z的颜色复制给y。
- z左右子树均为null,x=null,记录y.color=z.color
- 此时,y.color是实际删除结点的颜色,也就是这个颜色可能破坏了红黑树的性质。
- x指向需要调整的结点。
RB-DELETE(T,z)
y = z
y-original-color = z.color
if z.left == T.nil // z's left subtree is empty
x = z.right
RB-TRANSPLANT(T, z, z.right) // replace z to be it's right subtree
else if z.right == T.nil
x = z.left
RB-TRANSPLANT(T, z, z.left)
else y = TREE-MINIMUM(z.right) // z has two children then find successor
y-original-color = y.color
x = y.right // get y's right subtree
if y.p == z // y is z's very right child
x.p = y
else RB-TRANSPLANT(T, y, x) // replace y to be x
y.right = z.right // set r to be y's right subtree
y.right.p = y // set y to be r's parent
RB-TRANSPLANT(T, z, y) // replace z to be y
y.left = z.left
y.left.p = y
y.color = z.color
if y-original-color == BLACK // if black node is deleted then fix up
RB-DELETE-FIXUP(T,x)
- 重新着色和旋转
- 实际删除结点y的颜色y.color,如果是红色则不会破坏红黑树性质(y要么无子节点,要么只有一个子节点,如果有两个节点则又变成删除后继节点了,这个后继节点要么无子节点要么也是只有一个子节点)。
- y的颜色y.color是黑色破坏红黑树性质。
- 如果y是原来的根结点,而y的一个红孩子成为新的根结点,违反了性质2;
- x和x.p是红色的,违反了性质4;
- 在树中移动y导致先前包含y的任何简单路径上黑结点个数少1,违反性质5。
RB-DELETE-FIXUP(T,x)
while x != T.nil and x.color == BLACK
if x == x.p.left
w = x.p.right // w is x's brother
if w.color == RED
w.color = BLACK //case 1
x.p.color = RED //case 1
RB-ROTATE-LEFT(T, x.p) //case 1
w = x.p.right //case 1
if w.right.color == BLACK and w.left.color == BLACK
w.color = RED //case 2
x = x.p //case 2
else if w.right.color == BLACK
w.left.color = BLACK //case 3
w.color = RED //case 3
RB-ROTATE-RIGHT(T, w) //case 3
w = x.p.right //case 3
w.color = x.p.color //case 4
x.p.color = BLACK //case 4
w.right.color = BLACK //case 4
RB-ROTATE-LEFT(T, x.p) //case 4
x = T.root //case 4
else (same as the previous "if" clause with "right" and "left" extranged.)
x.color = BLACK
x结点操作可能情况
情况1:x的兄弟结点w是红色
操作方法:设x.p是x的父结点。改变w和x.p的颜色,然后对x.p做一次左旋。转换为情况2、情况3或情况4处理。
情况2:x的兄弟结点w是黑色的,而且w的两个子结点都是黑色的
操作方法:设x.p是x的父结点。x和w去掉一重黑色,x.p上补偿一重黑色。x上移。
情况3:x的兄弟结点w是黑色的,w的左孩子是红色的,w的右孩子是黑色的
操作方法:交换w和其左孩子w.left的颜色,然后对w进行右旋。转换为情况4。
情况4:x的兄弟结点w是黑色的,且w的右孩子是红色的
操作方法:重新着色和对xp做一次左旋。符合红黑树性质,结束程序。
如图所示:
比较一下平衡二叉树和红黑树
- 插入节点:AVL和RB-Tree都是最多两次树旋转来实现复衡rebalance,旋转的量级是O(1)。
- 删除节点:AVL需要维护从被删除节点到根节点root这条路径上所有节点的平衡,旋转的量级为O(logN),而RB-Tree最多只需要旋转3次实现复衡,只需O(1),显然RB-Tree删除节点的rebalance的效率更高。注意,必须在数据量比较大的情况下其效率才能显现出来。
- 搜索节点:AVL高度平衡(也叫高度平衡树),因此AVL的Search效率更高。通过对任意一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径比其他路径长2倍,因而是近似于平衡的。
红黑树的查询性能略微逊色于AVL树,因为其比AVL树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的AVL树最多多一次比较,但是,红黑树在插入和删除上优于AVL树,AVL树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于AVL树为了维持平衡的开销要小得多
PHP实现
代码:
<?php
class RedBlackNode
{
public $key;
public $parent;
public $left;
public $right;
public $color;
public function __construct($key)
{
$this->key = $key;
$this->parent = null;
$this->left = null;
$this->right = null;
$this->color = RedBlackTree::RED;
}
}
class RedBlackTree
{
private $root;
const RED = 'red';
const BLACK = 'black';
private function parentOf(RedBlackNode $node)
{
return empty($node) ? null : $node->parent;
}
private function colorOf(RedBlackNode $node)
{
return empty($node) ? self::BLACK : $node->color;
}
private function isRed(RedBlackNode $node)
{
return empty($node) ? false : (self::RED == $node->color ? true : false);
}
private function isBlack(RedBlackNode $node)
{
return empty($node) ? false : (self::BLACK == $node->color ? true : false);
}
private function setBlack(RedBlackNode $node)
{
if (!empty($node)) {
$node->color = self::BLACK;
}
}
private function setRed(RedBlackNode $node)
{
if (!empty($node)) {
$node->color = self::RED;
}
}
private function setParent(RedBlackNode $node, RedBlackNode $parent)
{
if (!empty($node)) {
$node->parent = $parent;
}
}
private function setColor(RedBlackNode $node, $color)
{
if (!empty($node)) {
$node->color = $color;
}
}
private function inOrderTraverse($tree, $depth=0) // 注意 $tree 也可能是 null
{
if (!empty($tree)) {
$this->inOrderTraverse($tree->left, $depth + 1);
for ($i=0; $i < $depth; $i++) {
echo '-';
}
echo "{$tree->key}({$tree->color})\n"; // --6(black)
$this->inOrderTraverse($tree->right, $depth + 1);
}
}
public function inOrder()
{
$this->inOrderTraverse($this->root);
}
// 为便于理解,结合附图 命名 $x
private function searchNode(RedBlackNode $x, $key)
{
if (empty($x)) {
return $x; // null
}
if($key < $x->key) {
return $this->searchNode($x->left, $key);
} elseif ($key > $x->key) {
return $this->searchNode($x->right, $key);
} else {
return $x;
}
}
public function search($key)
{
return $this->searchNode($this->root, $key);
}
/*
* 对红黑树的节点(x)进行左旋转
*
* 左旋示意图(对节点x进行左旋):
* px px
* / /
* x y
* / \ --(左旋)--> / \
* lx y x ry
* / \ / \
* ly ry lx ly
*
*
*/
private function leftRotate(RedBlackNode $x)
{
$y = $x->right;
$x->right = $y->left;
if (!empty($y->left)) {
$y->left->parent = $x;
}
$y->parent = $x->parent;
if (empty($x->parent)) { // $x 是根节点
$this->root = $y;
} else {
if ($x == $x->parent->left) { // $x 是其父亲的左孩子
$x->parent->left = $y;
} else { // $x 是其父亲的右孩子
$x->parent->right = $y;
}
}
$y->left = $x;
$x->parent = $y;
}
/*
* 对红黑树的节点(y)进行右旋转
*
* 右旋示意图(对节点y进行左旋):
* py py
* / /
* y x
* / \ --(右旋)-. / \
* x ry lx y
* / \ / \
* lx rx rx ry
*
*/
private function rightRotate(RedBlackNode $y)
{
$x = $y->left;
$y->left = $x->right;
if (!empty($x->right)) {
$x->right->parent = $y;
}
$x->parent = $y->parent;
if (empty($y->parent)) {
$this->root = $x;
} else {
if ($y == $y->parent->right) {
$y->parent->right = $x;
} else {
$y->parent->left = $x;
}
}
$x->right = $y;
$y->parent = $x;
}
private function insertFixUp(RedBlackNode $node)
{
while (!empty($parent = $this->parentOf($node)) && $this->isRed($parent)) {
$grandparent = $this->parentOf($parent);
if ($parent == $grandparent->left) {
$uncle = $grandparent->right;
// CASE 1: 叔叔是红色节点
if (!empty($uncle) && $this->isRed($uncle)) {
$this->setBlack($parent);
$this->setBlack($uncle);
$this->setRed($grandparent);
$node = $grandparent;
continue;
}
// CASE 2: 叔叔结点是黑色,且当前结点是右子节点
if ($node == $parent->right) {
$this->leftRotate($parent);
$tmp = $node;
$node = $parent;
$parent = $tmp;
}
// CASE 3: 叔叔结点是黑色,而且当前结点是父节点的左子节点
$this->setRed($grandparent);
$this->setBlack($parent);
$this->rightRotate($grandparent);
}
else {
$uncle = $grandparent->left;
// CASE 1
if (!empty($uncle) && $this->isRed($uncle)) {
$this->setBlack($parent);
$this->setBlack($uncle);
$this->setRed($grandparent);
$node = $grandparent;
continue;
}
// CASE 2
if ($node == $parent->left) {
$this->rightRotate($parent);
$tmp = $node;
$node = $parent;
$parent = $tmp;
}
// CASE 3
$this->setRed($grandparent);
$this->setBlack($parent);
$this->leftRotate($grandparent);
}
}
$this->setBlack($this->root);
}
private function insertNode(RedBlackNode $node)
{
$y = null;
$x = $this->root;
while (!empty($x)) {
$y = $x;
if ($node->key < $x->key) {
$x = $x->left;
} else {
$x = $x->right;
}
}
$node->parent = $y;
if (!empty($y)) {
if ($node->key < $y->key) {
$y->left = $node;
} else {
$y->right = $node;
}
} else {
$this->root = $node;
}
$this->insertFixUp($node);
}
public function insert(RedBlackNode $node)
{
$this->insertNode($node);
}
private function deleteFixUp($node, RedBlackNode $parent)
{
while ((empty($node) || $this->isBlack($node)) && ($node != $this->root)) {
if ($node == $parent->left) {
$sibling = $parent->right;
// Case 1: x的兄弟w是红色的
if ($this->isRed($sibling)) {
$this->setBlack($sibling);
$this->setRed($parent);
$this->leftRotate($parent);
$sibling = $parent->right;
}
// Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的
if (
(empty($sibling->left) || $this->isBlack($sibling->left)) &&
(empty($sibling->right) || $this->isBlack($sibling->right))
) {
$this->setRed($sibling);
$node = $parent;
$parent = $this->parentOf($node);
} else {
// Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。
if (empty($sibling->right) || $this->isBlack($sibling->right)) {
$this->setBlack($sibling->left);
$this->setRed($sibling);
$this->rightRotate($sibling);
$sibling = $parent->right;
}
// Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
$this->setColor($sibling, $parent);
$this->setBlack($sibling->right);
$this->setBlack($parent);
$this->leftRotate($parent);
$node = $this->root;
break;
}
} else {
$sibling = $parent->left;
// Case 1: x的兄弟w是红色的
if ($this->isRed($sibling)) {
$this->setBlack($sibling);
$this->setRed($parent);
$this->rightRotate($parent);
$sibling = $parent->left;
}
// Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的
if (
(empty($sibling->right) || $this->isBlack($sibling->right)) &&
(empty($sibling->left) || $this->isBlack($sibling->left))
) {
$this->setRed($sibling);
$node = $parent;
$parent = $this->parentOf($node);
} else {
// Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。
if (empty($sibling->left) || $this->isBlack($sibling->left)) {
$this->setBlack($sibling->right);
$this->setRed($sibling);
$this->leftRotate($sibling);
$sibling = $parent->left;
}
// Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
$this->setColor($sibling, $parent);
$this->setBlack($sibling->left);
$this->setBlack($parent);
$this->rightRotate($parent);
$node = $this->root;
break;
}
}
}
if (!empty($node)) {
$this->setBlack($node);
}
}
private function deleteNode(RedBlackNode $node)
{
// 被删除节点的"左右孩子都不为空"的情况。
if (!empty($node->left) && !empty($node->right)) {
// 被删节点的后继节点。(称为"取代节点")
// 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。
$replace = $node;
$replace = $replace->right;
while (!empty($replace->left)) {
$replace = $replace->left;
}
// "node节点"不是根节点(只有根节点不存在父节点)
if (!empty($this->parentOf($node))) {
if ($node == $this->parentOf($node)->left) {
$this->parentOf($node)->left = $replace;
} else {
$this->parentOf($node)->right = $replace;
}
} else {
$this->root = $replace;
}
// child是"取代节点"的右孩子,也是需要"调整的节点"。
// "取代节点"肯定不存在左孩子!因为它是一个后继节点。
$child = $replace->right;
$parent = $this->parentOf($replace);
$color = $this->colorOf($replace);
// "被删除节点"是"它的后继节点的父节点"
if ($node == $parent) {
$parent = $replace;
} else {
if (!empty($child)) {
$this->setParent($child, $parent);
}
$parent->left = $child;
// $replace 准备接管 $node
$replace->right = $node->right;
$this->setParent($node->right, $replace);
}
// $replace 接管 $node
$replace->parent = $node->parent;
$replace->color = $node->color;
$replace->left = $node->left;
$node->left->parent = $replace;
if ($color == self::BLACK) {
$this->deleteFixUp($child, $parent);
}
$node = null; // 释放 $node 节点
return ;
}
if (!empty($node->left)) {
$child = $node->left;
} else {
$child = $node->right;
}
$parent = $node->parent;
// 保存"取代节点"的颜色
$color = $node->color;
if (!empty($child)) {
$child->parent = $parent;
}
// "node节点"不是根节点
if (!empty($parent)) {
if ($node == $parent->left) {
$parent->left = $child;
} else {
$parent->right = $child;
}
} else {
$this->root = $child;
}
if ($color == self::BLACK) {
$this->deleteFixUp($child, $parent);
}
$node = null;
}
public function delete($key)
{
$node = $this->searchNode($this->root, $key);
if (!empty($node)) {
$this->deleteNode($node);
}
}
}
$RedBlackTree = new RedBlackTree();
$RedBlackTree->insert(new RedBlackNode(6));
$RedBlackTree->insert(new RedBlackNode(1));
$RedBlackTree->insert(new RedBlackNode(3));
$RedBlackTree->insert(new RedBlackNode(5));
$RedBlackTree->insert(new RedBlackNode(2));
$RedBlackTree->insert(new RedBlackNode(8));
$RedBlackTree->insert(new RedBlackNode(12));
$RedBlackTree->insert(new RedBlackNode(15));
$RedBlackTree->insert(new RedBlackNode(20));
$RedBlackTree->insert(new RedBlackNode(16));
echo '插入多个节点后的情况:', PHP_EOL;
$RedBlackTree->inOrder();
$RedBlackTree->delete(12);
$RedBlackTree->delete(1);
$RedBlackTree->delete(3);
echo '删除几个节点后的情况:', PHP_EOL;
$RedBlackTree->inOrder();
输出:
插入多个节点后的情况:
--1(black)
---2(red)
-3(red)
--5(black)
6(black)
--8(black)
-12(red)
---15(red)
--16(black)
---20(red)
删除几个节点后的情况:
--2(red)
-5(black)
6(black)
--8(black)
-15(red)
--16(black)
---20(red)
【参考】
- [1]《算法导论》
- [2] https://www.jianshu.com/p/9fc6f30936e2
- [3] https://blog.csdn.net/weixin_42315706/article/details/112317370
- [4] https://blog.csdn.net/rekingman/article/details/89792463