帶你深入理解STL之RBTree
最近一直忙於校招的筆試,STL的深入理解系列也耽擱了好幾天,再加上!紅黑樹真的是超級超級難理解,超級超級複雜,參考了好多部落格上的大神的理解才稍微明白一點,勉強入個門,下面請以一個菜鳥的角度跟著我一起學習STL的紅黑樹吧。
概述
紅黑樹是平衡二叉搜尋樹的一種,其通過特定的操作來保持二叉查詢樹的平衡。首先,我們來複習一下二叉查詢樹的知識,建議如果對二叉查詢樹不理解的先去搜一下相關部落格來了解一下。
二叉搜尋樹是指一個空樹或者具有以下性質的二叉樹:
- 任意節點的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
- 任意節點的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
- 任意節點的左、右子樹也分別為二叉查詢樹;
- 沒有鍵值相等的節點
我們知道,一顆由n個節點隨機構造的二叉搜尋樹的高度為logn,但是,由於輸入值往往不夠隨機,導致二叉搜尋樹可能失去平衡,造成搜尋效率低下的情況。從而,引出了平衡二叉搜尋樹的概念。對於“平衡”這個約束不同的結構有不同的規定,如AVL樹要求任何節點的兩個子樹的高度最大差別為1,可謂是高度平衡啊;而紅黑樹僅僅確保沒有一條路徑會比其他路徑長出兩倍,因而達到接近平衡的目的。紅黑數不僅是一個平衡二叉搜尋樹,而且還定義了相當多的約束來確保插入和刪除等操作後能達到平衡。
那麼,紅黑樹究竟是怎麼定義,來使得能夠達到平衡的目的呢?我們接著看下去。
紅黑樹的定義
紅黑樹既然屬於二叉搜尋樹的一種,當然需要滿足上述二叉搜尋樹的性質,除此之外,紅黑樹還為每一個節點增加了一個儲存位來表示節點的顏色屬性,它可以為red或者black,通過對任何一條從根到葉子節點的路徑上每個點進行著色方式的限制,來確保沒有一條路徑會比其他路徑長出兩倍,因而達到接近平衡的目的。
那麼,紅黑樹是如何進行著色的呢?下面引出了紅黑樹的五條性質:
- 每個節點或者是黑色,或者是紅色。
- 根節點是黑色。
- 每個葉子節點(NIL)是黑色。 [注意:這裡葉子節點,是指為空(NIL或NULL)的葉子節點!]
- 如果一個節點是紅色的,則它的子節點必須是黑色的。
- 從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
正是這五條性質,使得紅黑樹的高度能保持在logn,從而達到平衡的目的,進而使得其在查詢、插入和刪除的時間複雜度最壞為O(logn),下面就是一棵典型的紅黑樹。
紅黑樹的節點結構
紅黑樹的節點在二叉樹的節點結構上增加了顏色屬性,而且,為了更好的進行插入和刪除操作,進而增加了指向父節點的指標。為了更好的彈性,STL紅黑樹的節點採用雙層設計,將不依賴模板的引數提取出來,作為base結構,然後用帶模板的節點結構取繼承它。下面是紅黑樹節點結構的原始碼:
typedef bool __rb_tree_color_type;
const __rb_tree_color_type __rb_tree_red = false; // 紅色為 0
const __rb_tree_color_type __rb_tree_black = true; // 黑色為 1
struct __rb_tree_node_base
{
typedef __rb_tree_color_type color_type;
typedef __rb_tree_node_base* base_ptr;
color_type color; // 節點顏色
base_ptr parent; // 指向父節點
base_ptr left; // 指向左子節點
base_ptr right; // 指向右子節點
// 一直往左走,就能找到紅黑樹的最小值節點
// 二叉搜尋樹的性質
static base_ptr minimum(base_ptr x)
{
while (x->left != 0) x = x->left;
return x;
}
// 一直往右走,就能找到紅黑樹的最大值節點
// 二叉搜尋樹的性質
static base_ptr maximum(base_ptr x)
{
while (x->right != 0) x = x->right;
return x;
}
};
// 真正的節點定義,採用雙層節點結構
// 基類中不包含模板引數
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{
typedef __rb_tree_node<Value>* link_type;
Value value_field; // 節點實值
};
紅黑樹的迭代器
為了將RBtree實現為一個泛型容器,迭代器的設計很關鍵。我們要考慮它的型別,以及前進(increment)、後退(devrement)、提領(dereference)和成員訪問(member access)等操作。
迭代器和節點一樣,採用雙層設計,STL紅黑樹的節點__rb_tree_node繼承於__rb_tree_node_base;STL的迭代器結構__rb_tree_iterator繼承於__rb_tree_base_iterator,我們可以用一張圖來解釋這樣的設計目的。
將這些分開設計,可以保證對節點和迭代器的操作更具有彈性。下面來看迭代器的原始碼:
struct __rb_tree_base_iterator
{
typedef __rb_tree_node_base::base_ptr base_ptr;
typedef bidirectional_iterator_tag iterator_category;
typedef ptrdiff_t difference_type;
base_ptr node; // 用來連線紅黑樹的節點
// 尋找該節點的後繼節點上
void increment()
{
if (node->right != 0) { // 如果存在右子節點
node = node->right; // 直接跳到右子節點上
while (node->left != 0) // 然後一直往左子樹走,直到左子樹為空
node = node->left;
}
else { // 沒有右子節點
base_ptr y = node->parent; // 找出父節點
while (node == y->right) { // 如果該節點一直為它的父節點的右子節點
node = y; // 就一直往上找,直到不為右子節點為止
y = y->parent;
}
if (node->right != y) // 若此時該節點不為它的父節點的右子節點
node = y; // 此時的父節點即為要找的後繼節點
// 否則此時的node即為要找的後繼節點,此為特殊情況,如下
// 我們要尋找根節點的下一個節點,而根節點沒有右子節點
// 此種情況需要配合rbtree的header節點的特殊設計,後面會講到
}
}
// 尋找該節點你的前置節點
void decrement()
{
if (node->color == __rb_tree_red && // 如果此節點是紅節點
node->parent->parent == node) // 且父節點的父節點等於自己
node = node->right; // 則其右子節點即為其前置節點
// 以上情況發生在node為header時,即node為end()時
// 注意:header的右子節點為mostright,指向整棵樹的max節點,後面會有解釋
else if (node->left != 0) { // 如果存在左子節點
base_ptr y = node->left; // 跳到左子節點上
while (y->right != 0) // 然後一直往右找,知道右子樹為空
y = y->right;
node = y; // 則找到前置節點
}
else { // 如果該節點不存在左子節點
base_ptr y = node->parent; // 跳到它的父節點上
while (node == y->left) { // 如果它等於它的父子節點的左子節點
node = y; // 則一直往上查詢
y = y->parent;
} // 直到它不為父節點的左子節點未知
node = y; // 此時他的父節點即為要找的前置節點
}
}
}
template <class Value, class Ref, class Ptr>
struct __rb_tree_iterator : public __rb_tree_base_iterator
{
// 配合迭代器萃取機制的一些宣告
typedef Value value_type;
typedef Ref reference;
typedef Ptr pointer;
typedef __rb_tree_iterator<Value, Value&, Value*> iterator;
typedef __rb_tree_iterator<Value, const Value&, const Value*> const_iterator;
typedef __rb_tree_iterator<Value, Ref, Ptr> self;
typedef __rb_tree_node<Value>* link_type;
// 迭代器的建構函式
__rb_tree_iterator() {}
__rb_tree_iterator(link_type x) { node = x; }
__rb_tree_iterator(const iterator& it) { node = it.node; }
// 提領和成員訪問函式,過載了*和->操作符
reference operator*() const { return link_type(node)->value_field; }
pointer operator->() const { return &(operator*()); }
// 前置++和後置++
self& operator++() { increment(); return *this; }
self operator++(int) {
self tmp = *this;
increment(); // 直接呼叫increment函式
return tmp;
}
// 前置--和後置--
self& operator--() { decrement(); return *this; }
self operator--(int) {
self tmp = *this;
decrement(); // 直接呼叫decrement函式
return tmp;
}
};
在上述原始碼中,一直提到STL RBTree特殊節點header的設計,這個會在RBTree結構中講到,下面跟著我一起繼續往下看吧。
紅黑樹的資料結構
有了上面的節點和迭代器設計,就能很好的定義出一顆RBTree了。廢話不多說,一步一步來剖析原始碼吧。
template <class Key, class Value, class KeyOfValue, class Compare,
class Alloc = alloc>
class rb_tree {
protected:
typedef void* void_pointer;
typedef __rb_tree_node_base* base_ptr;
typedef __rb_tree_node<Value> rb_tree_node;
typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator; // 專屬配置器
typedef __rb_tree_color_type color_type;
public:
// 一些型別宣告
typedef Key key_type;
typedef Value value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef rb_tree_node* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
// RB-tree的資料結構
size_type node_count; // 記錄樹的節點個數
link_type header; // header節點設計
Compare key_compare; // 節點間的鍵值大小比較準則
// 以下三個函式用來取得header的成員
link_type& root() const { return (link_type&) header->parent; }
link_type& leftmost() const { return (link_type&) header->left; }
link_type& rightmost() const { return (link_type&) header->right; }
// 以下六個函式用來取得節點的成員
static link_type& left(link_type x) { return (link_type&)(x->left); }
static link_type& right(link_type x) { return (link_type&)(x->right); }
static link_type& parent(link_type x) { return (link_type&)(x->parent); }
static reference value(link_type x) { return x->value_field; }
static const Key& key(link_type x) { return KeyOfValue()(value(x)); }
static color_type& color(link_type x) { return (color_type&)(x->color); }
// 以下六個函式用來取得節點的成員,由於雙層設計,導致這裡需要兩個定義
static link_type& left(base_ptr x) { return (link_type&)(x->left); }
static link_type& right(base_ptr x) { return (link_type&)(x->right); }
static link_type& parent(base_ptr x) { return (link_type&)(x->parent); }
static reference value(base_ptr x) { return ((link_type)x)->value_field; }
static const Key& key(base_ptr x) { return KeyOfValue()(value(link_type(x)));}
static color_type& color(base_ptr x) { return (color_type&)(link_type(x)->color); }
// 求取極大值和極小值,這裡直接呼叫節點結構的函式極可
static link_type minimum(link_type x) {
return (link_type) __rb_tree_node_base::minimum(x);
}
static link_type maximum(link_type x) {
return (link_type) __rb_tree_node_base::maximum(x);
}
public:
// RBTree的迭代器定義
typedef __rb_tree_iterator<value_type, reference, pointer> iterator;
typedef __rb_tree_iterator<value_type, const_reference, const_pointer>
const_iterator;
public:
Compare key_comp() const { return key_compare; } // 由於紅黑樹自帶排序功能,所以必須傳入一個比較器函式
iterator begin() { return leftmost(); } // RBTree的起始節點為左邊最小值節點
const_iterator begin() const { return leftmost(); }
iterator end() { return header; } // RBTree的終止節點為右邊最大值節點
const_iterator end() const { return header; }
bool empty() const { return node_count == 0; } // 判斷紅黑樹是否為空
size_type size() const { return node_count; } // 獲取紅黑樹的節點個數
size_type max_size() const { return size_type(-1); } // 獲取紅黑樹的最大節點個數,
// 沒有容量的概念,故為sizetype最大值
};
我們看到,在RBTree的資料結構中,定義了RBTree節點和迭代器,然後添加了header節點,以及node_count引數,其他都是一下簡單的函式宣告和型別宣告。除此之外,並沒有過多的增加東西。這裡理解起來還是比較簡單,至於header有什麼作用,請繼續往下看。
紅黑樹的構造與記憶體管理
紅黑樹的建構函式
紅黑樹的空建構函式將建立一個空樹,此”空樹“非彼二叉樹的空樹也。空建構函式首先配置一個節點空間,使header指向該節點空間,然後將header的leftmost和rightmost指向自己,父節點指向0。非空的STL RBTree中,header和root之間互為父節點,然後header的leftmost始終指向該樹的最小值節點,rightmost始終指向該樹的最大值節點,其示例圖如下(左圖為空樹,右圖為非空樹):
下面來看看它的建構函式程式碼吧:
template <class Key, class Value, class KeyOfValue, class Compare,
class Alloc = alloc>
class rb_tree {
// 這部分程式碼是從紅黑樹的結構定義中提取出來的
protected:
typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator; // 專屬配置器
link_type get_node() { return rb_tree_node_allocator::allocate(); } // 配置空間
link_type create_node(const value_type& x) {
link_type tmp = get_node(); // 配置空間
__STL_TRY {
construct(&tmp->value_field, x); // 構造內容
}
__STL_UNWIND(put_node(tmp));
return tmp;
}
// 初始化函式,用來初始化一棵RBTree
void init() {
header = get_node(); // 產生一個節點空間,令header指向它
color(header) = __rb_tree_red; // 令header為紅色,用來區分header和root
root() = 0;
leftmost() = header; // 令header的左子節點為其自己
rightmost() = header; // 令header的右子節點為其自己
}
// 真正的預設建構函式
rb_tree(const Compare& comp = Compare())
: node_count(0), key_compare(comp) { init(); } // 直接呼叫初始化函式
// 帶參建構函式,以另一棵RBTree為初值來初始化
rb_tree(const rb_tree<Key, Value, KeyOfValue, Compare, Alloc>& x)
: node_count(0), key_compare(x.key_compare)
{
header = get_node(); // 產生一個節點空間,令 header 指向它
color(header) = __rb_tree_red; // 令 header 為紅色
if (x.root() == 0) { // 如果 x 是個空白樹
root() = 0;
leftmost() = header; // 令 header 的左子節點為自己。
rightmost() = header; // 令 header 的右子節點為自己。
}
else { // x 不是一個空白樹
__STL_TRY {
root() = __copy(x.root(), header); //呼叫copy函式
}
__STL_UNWIND(put_node(header));
leftmost() = minimum(root()); // 令 header 的左子節點為最小節點
rightmost() = maximum(root()); // 令 header 的右子節點為最大節點
}
node_count = x.node_count;
}
// copy函式定義如下
template <class K, class V, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<K, V, KeyOfValue, Compare, Alloc>::link_type
rb_tree<K, V, KeyOfValue, Compare, Alloc>::__copy(link_type x, link_type p) {
link_type top = clone_node(x); // 克隆root節點
top->parent = p; // 將root節點父節點指向p
// 以下為非遞迴的二叉樹複製過程
__STL_TRY {
if (x->right)
top->right = __copy(right(x), top); // 一直copy右子節點
p = top;
x = left(x); // 取左節點
while (x != 0) { // 左子節點不為空
link_type y = clone_node(x); // 克隆左子節點
p->left = y; // p的左子節點設為y
y->parent = p; // y的父節點設為p
if (x->right) // 如果左子節點還有右子節點,繼續複製
y->right = __copy(right(x), y);
p = y; // 直到沒有左子節點
x = left(x);
}
}
__STL_UNWIND(__erase(top));
return top;
}
// clone一個節點函式
link_type clone_node(link_type x) { // 複製一個節點(的值和色)
link_type tmp = create_node(x->value_field);
tmp->color = x->color;
tmp->left = 0;
tmp->right = 0;
return tmp;
}
}
紅黑樹的解構函式
解構函式負責清除和釋放紅黑樹上的每一個節點,並且初始化該紅黑樹為空樹,即恢復header為初始狀態
template <class Key, class Value, class KeyOfValue, class Compare,
class Alloc = alloc>
class rb_tree {
// 這部分程式碼是從紅黑樹的結構定義中提取出來的
protected:
void put_node(link_type p) { rb_tree_node_allocator::deallocate(p); }
// 析構一個節點
void destroy_node(link_type p) {
destroy(&p->value_field); // 析構內容
put_node(p); // 釋放空間
}
// 解構函式
~rb_tree() {
clear();
put_node(header);
}
// 清除整棵樹並初始化header節點
void clear() {
if (node_count != 0) {
__erase(root());
// 初始化Header節點
leftmost() = header;
root() = 0;
rightmost() = header;
node_count = 0;
}
}
// 清除RBTree的每個節點
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
void rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::__erase(link_type x) {
// 直接清,不用調平衡
while (x != 0) {
__erase(right(x));
link_type y = left(x);
destroy_node(x);
x = y;
}
}
}
紅黑樹的插入
紅黑樹在插入節點的時候,會破壞其平衡,這時候需要旋轉和變色來使紅黑樹重新達到平衡。這才是紅黑樹的精髓所在啊!所以這部分很長,大家耐心看,最好拿紙筆畫一下。
AVL樹的插入
我們先來看看AVL樹的旋轉問題。AVL樹中,插入一個節點後,有可能會破壞二叉樹的平衡,這時候可以通過左旋和右旋函式來使其重新恢復平衡。
通常破壞AVL樹平衡的插入情況有如下四種:
- 插入點位於X節點的左子節點的左子樹,左左
- 插入點位於X節點的右子節點的右子樹,右右
- 插入點位於X節點的左子節點的右子樹,左右
- 插入點位於X節點的右子節點的左子樹,右左
其中,情況1和2彼此對稱,稱為外側插入,可以採用單旋轉操作來調整;情況3和4彼此對稱,稱為內側插入,需要採用雙旋轉操作來調整。
外側插入
對於外側插入,我們通常採用一次旋轉就能解決問題,請看下面兩種外側插入的示意圖,其中左圖對應上述情況1,右圖對應上述情況2。
對於左圖的情況,採用一次右旋操作,可以使二叉樹重新恢復平衡,操作示意圖如下所示:
對於右圖的情況,採用一次左旋操作,可以使二叉樹重新恢復平衡,操作示意圖如下所示:
內側插入
對於內側插入,通常比較複雜,需要採用兩次旋轉操作來調整平衡。請看下面的內側插入的示意圖,其中左圖對於上述情況3,右圖對應上述情況4。
對於左圖的情況,需要先進行依次左旋操作,再進行一次右旋操作即可調整平衡,操作示意圖如下:
對於右圖的情況,需要先進行依次右旋操作,再進行一次左旋操作即可調整平衡,操作示意圖如下:
紅黑樹的插入
有了上面的單旋轉和雙旋轉知識,應該就很好理解紅黑數的平衡調整過程。紅黑樹在插入節點的時候,不僅需要考慮插入導致的不平衡,還要考慮顏色屬性是否滿足其性質要求。為了更清楚的表達整個過程,我們先來定義一下幾個標示量,使用X來表示一個新插入的節點,使用P來表示新插入節點的父節點,使用U來表示P節點的兄弟節點,使用G來表示P節點的父親節點,使用N代表NIL節點。於是,可以將插入情況分為一下幾類:
樹為空
新插入的節點為紅色,因為此時樹為空,那麼插入該節點後只需要把節點顏色調整為黑色即可。
父節點為黑
如果插入的節點的父節點為黑色,那麼插入一個紅節點將不會影響樹的平衡,直接插入即可。這裡就體現了黑紅樹的優勢,O(1)時間內就能判斷是否破壞了平衡,如果這裡是AVL樹的話就需要進行一次O(logn)判斷是否平衡。
父節點為紅色
當父節點為紅色的時候,就不滿足條件4,即父節點為紅色的時候,子節點必須為黑色,而新加入的節點為紅色。這個時候需要考慮如下兩種情況
1)叔父節點為紅色
這種情況下,需要將父節點和叔父節點變為黑色,將祖父節點變為紅色即可,不過如果祖父節點為紅色的話,還是違反了紅黑樹的性質,此時必須執行一個繼續向上迭代的程式來對紅黑樹的顏色進行調整,最後需要將根節點設定為黑色,如下圖所示:
2)叔父節點為黑色(Nil節點為黑)
這時候,需要對其進行旋轉操作,和上面AVL樹的旋轉一樣,分為4種情況,下面一一舉例來說明這四種情況:
- 左左,外側插入
- 左右,內側插入
- 右左,內側插入
- 右右,外側插入
紅黑樹的插入原始碼分析
紅黑樹的插入主要靠以下三個函式來實現:
// 左旋函式
inline void
__rb_tree_rotate_left(__rb_tree_node_base* x, __rb_tree_node_base*& root)
// 右旋函式
inline void
__rb_tree_rotate_right(__rb_tree_node_base* x, __rb_tree_node_base*& root)
// 插入後調節平衡函式
inline void
__rb_tree_rebalance(__rb_tree_node_base* x, __rb_tree_node_base*& root)
// 插入的核心函式
iterator __insert(base_ptr x, base_ptr y, const value_type& v);
上述三個函式就代表了上一小節示例圖中的左旋,右旋和變色的原始碼介面。
另外,STL還提供了兩個個介面函式,如下:
// 允許出現相同的值
iterator insert_equal(const value_type& x);
// 不允許出現相同的值
iterator insert_unique(iterator position, const value_type& x);
下面就針對這五個函式一一來分析一下他們的原始碼(以insert_unique為例):
// 此插入函式不允許重複
// 返回的是一個pair,第一個元素為紅黑樹的迭代器,指向新增節點
// 第二個元素表示插入操作是否成功的
template<class Key , class Value , class KeyOfValue , class Compare , class Alloc>
pair<typename rb_tree<Key , Value , KeyOfValue , Compare , Alloc>::iterator , bool>
rb_tree<Key , Value , KeyOfValue , Compare , Alloc>::insert_unique(const Value &v)
{
rb_tree_node* y = header; // 根節點root的父節點
rb_tree_node* x = root(); // 從根節點開始
bool comp = true;
while(x != 0)
{
y = x;
comp = key_compare(KeyOfValue()(v) , key(x)); // v鍵值小於目前節點之鍵值?
x = comp ? left(x) : right(x); // 遇“大”則往左,遇“小於或等於”則往右
}
// 離開while迴圈之後,y所指即插入點之父節點(此時的它必為葉節點)
iterator j = iterator(y); // 令迭代器j指向插入點之父節點y
if(comp) // 如果離開while迴圈時comp為真(表示遇“大”,將插入於左側)
{
if(j == begin()) // 如果插入點之父節點為最左節點
return pair<iterator , bool>(_insert(x , y , z) , true);// 呼叫_insert函式
else // 否則(插入點之父節點不為最左節點)
--j; // 調整j,回頭準備測試
}
if(key_compare(key(j.node) , KeyOfValue()(v) ))
// 新鍵值不與既有節點之鍵值重複,於是以下執行安插操作
return pair<iterator , bool>(_insert(x , y , z) , true);
// 以上,x為新值插入點,y為插入點之父節點,v為新值
// 進行至此,表示新值一定與樹中鍵值重複,那麼就不應該插入新值
return pair<iterator , bool>(j , false);
}
// 真正地插入執行程式 _insert()
// 返回新插入節點的迭代器
template<class Key , class Value , class KeyOfValue , class Compare , class Alloc>
typename<Key , Value , KeyOfValue , Compare , Alloc>::_insert(base_ptr x_ , base_ptr y_ , const Value &v)
{
// 引數x_ 為新值插入點,引數y_為插入點之父節點,引數v為新值
link_type x = (link_type) x_;
link_type y = (link_type) y_;
link_type z;
// key_compare 是鍵值大小比較準則。應該會是個function object
if(y == header || x != 0 || key_compare(KeyOfValue()(v) , key(y) ))
{
z = create_node(v); // 產生一個新節點
left(y) = z; // 這使得當y即為header時,leftmost() = z
if(y == header)
{
root() = z;
rightmost() = z;
}
else if(y == leftmost()) // 如果y為最左節點
leftmost() = z; // 維護leftmost(),使它永遠指向最左節點
}
else
{
z = create_node(v); // 產生一個新節點
right(y) = z; // 令新節點成為插入點之父節點y的右子節點
if(y == rightmost())
rightmost() = z; // 維護rightmost(),使它永遠指向最右節點
}
parent(z) = y; // 設定新節點的父節點
left(z) = 0; // 設定新節點的左子節點
right(z) = 0; // 設定新節點的右子節點
// 新節點的顏色將在_rb_tree_rebalance()設定(並調整)
_rb_tree_rebalance(z , header->parent); // 引數一為新增節點,引數二為根節點root
++node_count; // 節點數累加
return iterator(z); // 返回一個迭代器,指向新增節點
}
// 全域性函式
// 重新令樹形平衡(改變顏色及旋轉樹形)
// 引數一為新增節點,引數二為根節點root
inline void _rb_tree_rebalance(_rb_tree_node_base* x , _rb_tree_node_base*& root)
{
x->color = _rb_tree_red; //新節點必為紅
while(x != root && x->parent->color == _rb_tree_red) // 父節點為紅
{
if(x->parent == x->parent->parent->left) // 父節點為祖父節點之左子節點
{
_rb_tree_node_base* y = x->parent->parent->right; // 令y為伯父節點
if(y && y->color == _rb_tree_red) // 伯父節點存在,且為紅
{
x->parent->color = _rb_tree_black; // 更改父節點為黑色
y->color = _rb_tree_black; // 更改伯父節點為黑色
x->parent->parent->color = _rb_tree_red; // 更改祖父節點為紅色
x = x->parent->parent;
}
else // 無伯父節點,或伯父節點為黑色
{
if(x == x->parent->right) // 如果新節點為父節點之右子節點
{
x = x->parent;
_rb_tree_rotate_left(x , root); // 第一個引數為左旋點
}
x->parent->color = _rb_tree_black; // 改變顏色
x->parent->parent->color = _rb_tree_red;
_rb_tree_rotate_right(x->parent->parent , root); // 第一個引數為右旋點
}
}
else // 父節點為祖父節點之右子節點
{
_rb_tree_node_base* y = x->parent->parent->left; // 令y為伯父節點
if(y && y->color == _rb_tree_red) // 有伯父節點,且為紅
{
x->parent->color = _rb_tree_black; // 更改父節點為黑色
y->color = _rb_tree_black; // 更改伯父節點為黑色
x->parent->parent->color = _rb_tree_red; // 更改祖父節點為紅色
x = x->parent->parent; // 準備繼續往上層檢查
}
else // 無伯父節點,或伯父節點為黑色
{
if(x == x->parent->left) // 如果新節點為父節點之左子節點
{
x = x->parent;
_rb_tree_rotate_right(x , root); // 第一個引數為右旋點
}
x->parent->color = _rb_tree_black; // 改變顏色
x->parent->parent->color = _rb_tree_red;
_rb_tree_rotate_left(x->parent->parent , root); // 第一個引數為左旋點
}
}
}//while
root->color = _rb_tree_black; // 根節點永遠為黑色
}
// 左旋函式
inline void _rb_tree_rotate_left(_rb_tree_node_base* x , _rb_tree_node_base*& root)
{
// x 為旋轉點
_rb_tree_node_base* y = x->right; // 令y為旋轉點的右子節點
x->right = y->left;
if(y->left != 0)
y->left->parent = x; // 別忘了回馬槍設定父節點
y->parent = x->parent;
// 令y完全頂替x的地位(必須將x對其父節點的關係完全接收過來)
if(x == root) // x為根節點
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;
}
// 右旋函式
inline void _rb_tree_rotate_right(_rb_tree_node_base* x , _rb_tree_node_base*& root)
{
// x 為旋轉點
_rb_tree_node_base* y = x->left; // 令y為旋轉點的左子節點
x->left = y->right;
if(y->right != 0)
y->right->parent = x; // 別忘了回馬槍設定父節點
y->parent = x->parent;
// 令y完全頂替x的地位(必須將x對其父節點的關係完全接收過來)
if(x == root)
root = y;
else if(x == x->parent->right) // x為其父節點的右子節點
x->parent->right = y;
else // x為其父節點的左子節點
x->parent->left = y;
y->right = x;
x->parent = y;
}
紅黑樹的刪除
紅黑樹在刪除節點後,需要調整以使得紅黑樹保持平衡,由於刪除後調節平衡實在太複雜,本文就不做分析,只提供其介面函式。
如果對其感興趣的話,可以參考一下這篇博文:(圖解)紅黑樹的插入和刪除
// 刪除節點後調節平衡
inline __rb_tree_node_base*
__rb_tree_rebalance_for_erase(__rb_tree_node_base* z,
__rb_tree_node_base*& root,
__rb_tree_node_base*& leftmost,
__rb_tree_node_base*& rightmost);
STL為紅黑樹提供了以下刪除操作的節點函式:
// 刪除指定位置的節點
void erase(iterator position);
// 刪除迭代器區間位置內的節點
void erase(iterator first, iterator last);
// 清除所有的節點
void clear();
下面來看看它們的原始碼實現:
// 刪除指定位置的節點
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
inline void
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::erase(iterator position) {
// 刪除節點後需要使其重新恢復平衡
link_type y = (link_type) __rb_tree_rebalance_for_erase(position.node,
header->parent,
header->left,
header->right);
// 清除掉被刪除的節點,釋放記憶體
destroy_node(y);
--node_count;
}
// 清除掉區間內的節點
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
void rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::erase(iterator first,
iterator last) {
if (first == begin() && last == end())
clear();
else
while (first != last) erase(first++);
}
// 清除所有的節點
void clear() {
if (node_count != 0) {
// 這裡不需要呼叫上面的__rb_tree_rebalance_for_erase
// 而是直接呼叫不需要調節平衡的刪除節點函式,見下面
__erase(root());
leftmost() = header;
root() = 0;
rightmost() = header;
node_count = 0;
}
}
// 刪除紅黑樹的節點,刪除過程中不需要調節平衡
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
void rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::__erase(link_type x) {
while (x != 0) {
__erase(right(x));
link_type y = left(x);
destroy_node(x);
x = y;
}
}
紅黑樹的元素搜尋
find函式用於查詢是否存在鍵值為k的節點。
// 尋找RBTree中是否存在鍵值為k的節點
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::find(const Key& k) {
link_type y = header; // Last node which is not less than k.
link_type x = root(); // Current node.
while (x != 0)
// key_compare是節點鍵值大小比較函式
if (!key_compare(key(x), k))
// 如果節點x的鍵值大於k,則繼續往左子樹查詢
y = x, x = left(x); //
else
// 如果節點x的鍵值小於k,則繼續往右子樹查詢
x = right(x);
iterator j = iterator(y);
// y的鍵值不小於k,返回的時候需要判斷與k是相等還是小於
return (j == end() || key_compare(k, key(j.node))) ? end() : j;
}
另外,STL的紅黑樹還針對multiset和multimap提供了幾個搜尋函式,分別如下:
// 計算鍵值為x的節點的個數
size_type count(const key_type& x)
// 提供了查詢與某個鍵值相等的節點迭代器範圍
pair<iterator,iterator> equal_range(const key_type& x);
//返回不小於k的第一個節點迭代器
iterator lower_bound(const key_type& x);
//返回大於k的第一個節點迭代器
iterator upper_bound(const key_type& x);
其實現函式也一併貼出來,讓大家好理解一下吧。
// 計算RBTree的節點值為k的節點個數
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::size_type
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::count(const Key& k) {
pair<const_iterator, const_iterator> p = equal_range(k);
size_type n = 0;
distance(p.first, p.second, n);
return n;
}
// 查詢與鍵值k相等的節點迭代器範圍
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
inline pair<typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator,
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator>
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::equal_range(const Key& k) {
return pair<iterator, iterator>(lower_bound(k), upper_bound(k));
}
//返回不小於k的第一個節點迭代器
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::lower_bound(const Key& k) {
link_type y = header; /* Last node which is not less than k. */
link_type x = root(); /* Current node. */
while (x != 0)
if (!key_compare(key(x), k))
y = x, x = left(x);
else
x = right(x);
return iterator(y);
}
//返回大於k的第一個節點迭代器
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::upper_bound(const Key& k) {
link_type y = header; /* Last node which is greater than k. */
link_type x = root(); /* Current node. */
while (x != 0)
if (key_compare(k, key(x)))
y = x, x = left(x);
else
x = right(x);
return iterator(y);
}
參考:
相關推薦
帶你深入理解STL之RBTree
最近一直忙於校招的筆試,STL的深入理解系列也耽擱了好幾天,再加上!紅黑樹真的是超級超級難理解,超級超級複雜,參考了好多部落格上的大神的理解才稍微明白一點,勉強入個門,下面請以一個菜鳥的角度跟著我一起學習STL的紅黑樹吧。 概述 紅黑樹是平衡二叉搜尋樹的
帶你深入理解STL之List容器
上一篇部落格中介紹的vector和陣列類似,它擁有一段連續的記憶體空間,並且起始地址不變,很好的支援了隨機存取,但由於是連續空間,所以在中間進行插入、刪除等操作時都造成了記憶體塊的拷貝和移動,另外在記憶體空間不足時還需要重新申請一塊大記憶體來進行記憶體的拷貝。為
帶你深入理解STL之Vector容器
C++內建了陣列的型別,在使用陣列的時候,必須指定陣列的長度,一旦配置了就不能改變了,通常我們的做法是:儘量配置一個大的空間,以免不夠用,這樣做的缺點是比較浪費空間,預估空間不當會引起很多不便。 STL實現了一個Vector容器,該容器就是來改善陣列的缺點。v
帶你深入理解STL之Stack和Queue
上一篇部落格,帶你深入理解STL之Deque容器中詳細介紹了deque容器的原始碼實現方式。結合前面介紹的兩個容器vector和list,在使用的過程中,我們確實要知道在什麼情況下需要選擇恰當的容器來滿足需求和提升效率。一般選擇的準則有如下幾條: 如果需要隨
帶你深入理解STL之空間配置器(思維導圖+原始碼)
前不久把STL細看了一遍,由於看得太“認真”,忘了做筆記,歸納和總結這步漏掉了。於是為了加深印象,打算重看一遍,並記錄下來裡面的一些實現細節。方便以後能較好的複習它。 以前在專案中運用STL一般都不會涉及到空間配置器,可是,在STL的實現中,空間配置器是重中之
併發程式設計之美,帶你深入理解java多執行緒原理
1.什麼是多執行緒? 多執行緒是為了使得多個執行緒並行的工作以完成多項任務,以提高系統的效率。執行緒是在同一時間需要完成多項任務的時候被實現的。 2.瞭解多執行緒 瞭解多執行緒之前我們先搞清楚幾個重要的概念! 如上圖所示:對我們的專案有一個主記憶體,這個主記憶體裡面存放了我們的共享變數、方法區、堆中的物件等
教你寫Http框架(二)——三個樣例帶你深入理解AsyncTask
func implement oncreate 其它 層疊 worker dcl 例如 人員 這個標題大家不要奇怪,扯Http框架怎麽扯到AsyncTask去了,有兩個原因:首先是Http框架除了核心http理論外。其技術實現核心也是線程池 + 模板 +
一篇文章帶你深入理解Zookeeper
隨著網際網路技術的發展,大型網站需要的計算能力和儲存能力越來越高。網站架構逐漸從集中式轉變成分散式。 雖然分散式和集中式系統相比有很多優勢,比如能提供更強的計算、儲存能力,避免單點故障等問題。但是由於採用分散式部署的方式,就經常會出現網路故障等問題,並且如何在分散式系統中保證資料的一致性和可用性也是一個比較
阿里P7帶你深入理解Java虛擬機器總結——類初始化過程
類的初始化過程 非法向前引用 編譯器手機的順序是由語句在原始檔中出現的順序決定的,靜態語句塊中只能訪問到定義在靜態語句之前的變數,定義它之後的變數,可以賦值,但不能訪問 public class Test{ static{ i=0; system.out.print(
一篇文章帶你深入理解什麼是負載測試
介紹 任何軟體開發專案接近完成的時候,它可能已經通過無數次測試了,特別是在測試和開發同時發生的敏捷測試環境下。無論你已經進行過多少輪測試,一旦你的應用程式已接近完成,那麼只有一個辦法知道你的軟體是否可以滿足真實使用者群的實際需求,它就是負載測試。你可以使用負載
滴滴工程師帶你深入理解 TCP 握手分手全過程
after ets c51 proxy 還需要 2.3 afa raft 五個 本文作者:饒全成,中科院計算所碩士,滴滴出行後端研發工程師。 個人主頁:https://zhihu.com/people/raoquancheng
帶你深入理解Activity啟動模式(LaunchMode)
我們知道預設情況下,當我們多次啟動同一個activity時,系統會建立多個例項並把他們一個個放入任務棧,當我們按back鍵,這些activity又會一個個退出。在講activity的launchmode之前,我們有必要了解下“任務棧(Task Stack)”這個
帶你深入理解Android中的自定義屬性!!!
att omv world 過程 參數 and pla 開發 dimen 引言 對於自定義屬性,大家肯定都不陌生,遵循以下幾步,就可以實現: 1.自定義一個CustomView(extends View )類 2.編寫values/attrs.xml,在其中編寫styl
從JAVA記憶體到垃圾回收,帶你深入理解JVM
摘要:學過Java的程式設計師對JVM應該並不陌生,如果你沒有聽過,沒關係今天我帶你走進JVM的世界。程式設計師為什麼要學習JVM呢,其實不懂JVM也可以照樣寫出優質的程式碼,但是不懂JVM有可能別被面試官虐得體無完膚。 § 1.JAVA記憶體區域與記憶體溢位異常 § 1.1執行時資料區域 § 1.1.1
CocosCreator之KUOKUO帶你輕鬆理解直流電機PID調速系統
本次引擎2.0.5 編輯工具VSCode 神奇的腦洞浮現!CocosCreator如何模擬電機轉速控制呢? 哇咔咔!KUOKUO帶你學知識,0基礎也能看懂! 我相信我們在生活中都接觸過小的直流電機,玩具裡就有很多。 我們都知道對於直流電機,我們
學習Docker之10張圖帶你深入理解Docker容器和映象
剛開始接觸Docker之後,就對容器和映象的概念有所迷惑,上一篇也簡單的說了一下之前的見解,其實並不準確,在之後的學習中再加上網上找的資料,發現了下面的這一片博文,一定要多看幾遍,看完思考再看,就會對容器和映象有更深刻的認識。 【編者的話】本文用圖文
深入理解javascript之設計模式
rip 是我 解決問題 不想 接受 button move center 常識 設計模式 設計模式是命名、抽象和識別對可重用的面向對象設計實用的的通用設計結構。設計模式確定類和他們的實體、他們的角色和協作、還有他們的責任分配。 每個設計模式都聚焦於一個面向對象的設計難題
深入理解javascript之原型
undefine tor ace 對象實例 高級 code turn 三方 true 理解原型 原型是一個對象。其它對象能夠通過它實現屬性繼承。不論什麽一個對象都能夠成為繼承,全部對象在默認的情況下都有一個原型。由於原型本身也是對象,所以每一個原型自身又有一個原型
深入理解Java之線程池
我們 先進先出 tor cor null 討論 等於 影響 log 重要連接:http://www.cnblogs.com/dolphin0520/p/3958019.html 出處:http://www.cnblogs.com/dolphin0520/ 本文歸作者
深入理解JVM之JVM內存區域與內存分配
錯誤 銷毀 構造方法 初學 不存在 data 空閑 table fin 深入理解JVM之JVM內存區域與內存分配 在學習jvm的內存分配的時候,看到的這篇博客,該博客對jvm的內存分配總結的很好,同時也利用jvm的內存模型解釋了java程序中有關參數傳遞的問題。