紅黑樹原理淺談(附Linux核心原始碼註釋)
引言:紅黑樹(英語:Red–black tree)是一種
自平衡二叉查詢樹
,是在電腦科學中用到的一種資料結構,典型的用途是實現關聯陣列。它是在1972年由魯道夫·貝爾發明的,他稱之為"對稱二叉B樹",它現代的名字是在Leo J. Guibas和Robert Sedgewick於1978年寫的一篇論文中獲得的。它是複雜的,但它的操作有著良好的最壞情況執行時間,並且在實踐中是高效的:它可以在O(log n)
時間內做查詢
,插入
和刪除
,這裡的n是樹中元素的數目。——摘自維基百科
紅黑樹的性質:
紅黑樹是每個節點都帶有顏色屬性的二叉查詢樹,顏色為紅色或黑色。在二叉查詢樹強制一般要求以外,對於任何有效的紅黑樹有如下的額外要求:
1.節點是紅色或黑色。
2.根是黑色。
3.所有葉子都是黑色(葉子是NIL節點)。
4.每個紅色節點必須有兩個黑色的子節點。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點。)
5.從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點。
操作:
因為每一個紅黑樹也是一個特化的二叉查詢樹,因此紅黑樹上的只讀操作與普通二叉查詢樹上的只讀操作相同。然而,在紅黑樹上進行插入操作和刪除操作會導致不再匹配紅黑樹的性質。恢復紅黑樹的性質需要少量 O(log n)
的顏色變更(實際是非常快速的)和不超過三次樹旋轉(對於插入操作是兩次
O(log n)
次。
紅黑樹的應用
它的統計效能要好於平衡二叉樹(AVL樹),因此,紅黑樹在很多地方都有應用。比如
①JDK1.8中的TreeMap 或
②C++的STL中的set和map函式或
③Linux核心中的rbtree.h和rbtree.c
都有紅黑樹的應用,這些集合均提供了很好的效能。
什麼時候用AVL樹?什麼時候用紅黑樹?
首先AVL樹和紅黑樹都是基於BST樹(二叉搜尋樹),對於只讀操作均和BST樹相同,但BST樹連續插入順序遞增或遞減結點時會導致BST樹退化成連結串列,效能大幅度降低。BST順序插入如圖:
因此,AVL樹
①提升資料結構的複雜度
②提升演算法的複雜度
其中,紅黑樹為了降低原有演算法複雜度,就以提升資料結構複雜度來間接減輕其演算法複雜度。即在其結點中增加顏色屬性,顏色只能有紅色和黑色。在紅黑樹中只要任意結點左右孩子的黑色高度平衡(兩邊任意路徑黑色結點數量相同)即可且對於有些情況可以通過變色來代替旋轉從而減少一定的空間開銷。
①AVL樹採用平衡因子的絕對值<2來保證平衡,這種平衡是高度平衡的。要保證這種平衡需要更復雜的操作來維持。
②紅黑樹採用同一結點到葉結點的任意路徑黑色結點數量相同來保證平衡,這種平衡只能保證從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。保證這種平衡只需要較少的操作來維持。
下圖是從根到葉子的最長的可能路徑=最短的可能路徑的兩倍長的情況:
綜上所述:刪除結點最壞情況下,AVL樹需要旋轉的量級是O(log n)
,而紅黑樹最多隻需3次旋轉,只需要O(1)
的複雜度。
下圖是知乎大神提供的效能測試:
由此可見:
①紅黑樹由於插入刪除時較少的旋轉操作,對於需要頻繁進行插入刪除的場景效能比AVL樹好
②反之,對於不需要頻繁插入刪除的場景。由於AVL樹的高度平衡,使得同等結點數量下,AVL樹的高度比紅黑樹更低,使得AVL樹的查詢效能比紅黑樹好。
JDK1.8裡的TreeMapEntry對紅黑樹資料結構的定義
/**
* Node in the Tree. Doubles as a means to pass key-value pairs back to
* user (see Map.Entry).
*/
static final class TreeMapEntry<K,V> implements Map.Entry<K,V> {
K key;
V value;
TreeMapEntry<K,V> left = null;//左子樹
TreeMapEntry<K,V> right = null;//右子樹
TreeMapEntry<K,V> parent;//父節點
boolean color = BLACK;//預設為黑色
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
TreeMapEntry(K key, V value, TreeMapEntry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
/**
* Returns the key.
*
* @return the key
*/
public K getKey() {
return key;
}
/**
* Returns the value associated with the key.
*
* @return the value associated with the key
*/
public V getValue() {
return value;
}
/*...各種方法*/
Linux核心檔案rbtree.h中對紅黑樹的資料結構的定義
struct rb_node
{/*long是四個位元組,佔用共4x8=32bit,其中有兩位儲存父結點指標+自己的顏色值*/
unsigned long rb_parent_color; /*注意:此處儲存的是父指標+自己顏色*/
#define RB_RED 0
#define RB_BLACK 1
struct rb_node *rb_right;
struct rb_node *rb_left;
} /* __attribute__((aligned(sizeof(long))))*/;
/* The alignment might seem pointless, but allegedly CRIS needs it */
struct rb_root
{
struct rb_node *rb_node;
};
以及rbtree.h中在插入新結點時,初始化紅黑樹結點操作
static inline void rb_init_node(struct rb_node *rb)
{
rb->rb_parent_color = 0;
rb->rb_right = NULL;
rb->rb_left = NULL;
RB_CLEAR_NODE(rb);
}
在此需要先了解左旋和右旋的原理
結點E左旋操作:
(動圖來源於網路)
程式碼如下:
/*
* 左旋操作Linux c程式碼(來源於Linux核心rbtree.c)
*/
static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
{
struct rb_node *right = node->rb_right;
struct rb_node *parent = rb_parent(node);
if ((node->rb_right = right->rb_left)) //node的右指標指向node右孩子的左孩子
rb_set_parent(right->rb_left, node);//node右孩子的左孩子的父親指定為node
right->rb_left = node;
rb_set_parent(right, parent); //node右孩子祖父指定為node原父親
if (parent) //node原父親存在,即原node不是根結點
{
if (node == parent->rb_left)
parent->rb_left = right;
else
parent->rb_right = right;
}
else //原node是根結點
root->rb_node = right;
rb_set_parent(node, right); //node現任父親指定為node原右孩子
}
結點S右旋操作:
(動圖來源於網路,程式碼與左旋對調)
紅黑樹插入操作邏輯
紅黑樹刪除操作邏輯
Linux核心原始碼註釋解析
rbtree.c
/*
Red Black Trees
(C) 1999 Andrea Arcangeli <[email protected]>
(C) 2002 David Woodhouse <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
linux/lib/rbtree.c
*/
#include "rbtree.h"
/*
* 左旋操作
*/
static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
{
struct rb_node *right = node->rb_right;
struct rb_node *parent = rb_parent(node);
if ((node->rb_right = right->rb_left)) //node的右指標指向node右孩子的左孩子
rb_set_parent(right->rb_left, node);//node右孩子的左孩子的父親指定為node
right->rb_left = node;
rb_set_parent(right, parent); //node右孩子祖父指定為node原父親
if (parent) //node原父親存在,即原node不是根結點
{
if (node == parent->rb_left)
parent->rb_left = right;
else
parent->rb_right = right;
}
else //原node是根結點
root->rb_node = right;
rb_set_parent(node, right); //node現任父親指定為node原右孩子
}
/*
* 右旋操作
*/
static void __rb_rotate_right(struct rb_node *node, struct rb_root *root)
{
struct rb_node *left = node->rb_left;
struct rb_node *parent = rb_parent(node);
if ((node->rb_left = left->rb_right))
rb_set_parent(left->rb_right, node);
left->rb_right = node;
rb_set_parent(left, parent);
if (parent)
{
if (node == parent->rb_right)
parent->rb_right = left;
else
parent->rb_left = left;
}
else
root->rb_node = left;
rb_set_parent(node, left);
}
/*
* 插入結點操作
*/
void rb_insert_color(struct rb_node *node, struct rb_root *root)
{
struct rb_node *parent, *gparent;
//情況1,2:node不是根結點,即有父結點P且P是紅色的
while ((parent = rb_parent(node)) && rb_is_red(parent))
{
gparent = rb_parent(parent);
//P是祖父G的左孩子
if (parent == gparent->rb_left)
{
{
register struct rb_node *uncle = gparent->rb_right;
if (uncle && rb_is_red(uncle)) //情況3:node的叔父結點是紅色的
{
rb_set_black(uncle);
rb_set_black(parent);
rb_set_red(gparent);
node = gparent;
continue;
}
}
//情況4:node和父結點P是LR型(變成LL型)
if (parent->rb_right == node)
{
register struct rb_node *tmp;
__rb_rotate_left(parent, root);
tmp = parent;
parent = node;
node = tmp;
}
//情況5:node和父結點P是LL型
rb_set_black(parent);
rb_set_red(gparent);
__rb_rotate_right(gparent, root);
} else { //P是祖父G的右孩子,與上述情況對調
{
register struct rb_node *uncle = gparent->rb_left;
if (uncle && rb_is_red(uncle))
{
rb_set_black(uncle);
rb_set_black(parent);
rb_set_red(gparent);
node = gparent;
continue;
}
}
if (parent->rb_left == node)
{
register struct rb_node *tmp;
__rb_rotate_right(parent, root);
tmp = parent;
parent = node;
node = tmp;
}
rb_set_black(parent);
rb_set_red(gparent);
__rb_rotate_left(gparent, root);
}
}
//若node是根結點||node的父結點P是黑色的,則把根結點->黑色
rb_set_black(root->rb_node);
}
/*
* 刪除結點操作(Node和Child均是黑色的情況)
*/
static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,
struct rb_root *root)
{
struct rb_node *other;
//以下迴圈體條件node不是根結點&&(node非空||node是黑色的)
while ((!node || rb_is_black(node)) && node != root->rb_node)
{
if (parent->rb_left == node) //若node是父結點的左孩子
{
other = parent->rb_right;
if (rb_is_red(other)) //N的兄弟結點S是紅色的
{ //S由紅色->黑色;P由黑色->紅色;P左旋
rb_set_black(other);
rb_set_red(parent);
__rb_rotate_left(parent, root);
other = parent->rb_right;
} //SL黑色&&SR黑色
if ((!other->rb_left || rb_is_black(other->rb_left)) &&
(!other->rb_right || rb_is_black(other->rb_right)))
{
rb_set_red(other);
node = parent; //將P作為node帶入以下檢查程式
parent = rb_parent(node);
}
else
{ //SL是紅色(推斷),SR是黑色
if (!other->rb_right || rb_is_black(other->rb_right))
{ //SL由紅色->黑色;S由黑色->紅色;右旋S;P的新右孩子是SL
rb_set_black(other->rb_left);
rb_set_red(other);
__rb_rotate_right(other, root);
other = parent->rb_right;
} //S由黑色->P的顏色;P->黑色;SR由紅色->黑色;左旋P
rb_set_color(other, rb_color(parent));
rb_set_black(parent);
rb_set_black(other->rb_right);
__rb_rotate_left(parent, root);
node = root->rb_node;
break;
}
}
else //若node是父結點的右孩子,和上述情況對調
{
other = parent->rb_left;
if (rb_is_red(other))
{
rb_set_black(other);
rb_set_red(parent);
__rb_rotate_right(parent, root);
other = parent->rb_left;
}
if ((!other->rb_left || rb_is_black(other->rb_left)) &&
(!other->rb_right || rb_is_black(other->rb_right)))
{
rb_set_red(other);
node = parent;
parent = rb_parent(node);
}
else
{
if (!other->rb_left || rb_is_black(other->rb_left))
{
rb_set_black(other->rb_right);
rb_set_red(other);
__rb_rotate_left(other, root);
other = parent->rb_left;
}
rb_set_color(other, rb_color(parent));
rb_set_black(parent);
rb_set_black(other->rb_left);
__rb_rotate_right(parent, root);
node = root->rb_node;
break;
}
}
}
if (node)
rb_set_black(node);
}
/*
* 刪除紅黑樹結點,處理除Node和Child均是黑色以外的情況,最後單獨將這種情況用__rb_erase_color函式處理
*/
void rb_erase(struct rb_node *node, struct rb_root *root)
{
struct rb_node *child, *parent;
int color;
if (!node->rb_left) //1.刪除節點node左孩子為空
child = node->rb_right; //child為node右孩子
else if (!node->rb_right)//2.刪除節點node右孩子為空
child = node->rb_left; //child為node左孩子
else //3.刪除節點node子結點均非空,則需要找到其右子樹的最小的結點然後跟換兩個結點的值
{
struct rb_node *old = node, *left;
node = node->rb_right; //令node為原node的右孩子
while ((left = node->rb_left) != NULL) //不斷迴圈找到其最左的孩子(最小值結點)
node = left;
if (rb_parent(old)) { //若原node(old)有父結點
if (rb_parent(old)->rb_left == old) //若old是父結點的左孩子,則現在左孩子是新node
rb_parent(old)->rb_left = node;
else
rb_parent(old)->rb_right = node;//若old是父結點的右孩子,則現在右孩子是新node
} else
root->rb_node = node; //若原node(old)是根結點,則現在的根是新node
child = node->rb_right; //新node的右孩子的是child
parent = rb_parent(node);//新node的父親的是parent
color = rb_color(node);//新node的顏色是color
//以下將old替換為新node結點,同時將新node的非空子結點child成為P的左孩子
if (parent == old) {
parent = node;
} else {
if (child)
rb_set_parent(child, parent);
parent->rb_left = child; //P的左孩子由node變為child
node->rb_right = old->rb_right;
rb_set_parent(old->rb_right, node);
}
node->rb_parent_color = old->rb_parent_color; //Linux核心中紅黑樹資料結構儲存的是其父結點的顏色
node->rb_left = old->rb_left;
rb_set_parent(old->rb_left, node);
goto color; //goto到"color:"一行
}
//以下步驟是對換情況1和2的node和child兩結點,使child為node的新父親
parent = rb_parent(node);
color = rb_color(node);
if (child)
rb_set_parent(child, parent);
if (parent)
{
if (parent->rb_left == node)
parent->rb_left = child;
else
parent->rb_right = child;
}
else
root->rb_node = child;
//以上步驟是對換情況1和2的node和child兩結點,使child為node的新父親
color:
if (color == RB_BLACK) //若和child顛倒位置後的node結點是黑色
__rb_erase_color(child, parent, root); //將child帶入檢查程式
}
/*
* 替換結點函式(將結點victim替