<STL源碼剖析>導覽
關聯式容器
- 所謂關聯式容器,觀念上類似關聯式數據庫,每組數據都有一個鍵值(key)和一個實值(value)。當元素被插入到容器中時,容器內部結構(RB-Tree,或者是hash-table)便依照其鍵值大小,以某種特定規則將這個元素放置於適當位置。
一般而言,關聯式容器的內部結構是一個balanced binary tree(平衡二叉樹),以便獲得良好的搜尋效率.平衡二叉樹有許多種類,包含AVL-tree,RB-tree,AA-tree.
二叉搜索樹
- 所謂二叉搜索樹(binary search tree),可提供對數時間的元素插入和訪問。二叉搜索樹的節點放置規則是:任何節點的鍵值一定大於其左子樹中的每一個節點的鍵值,並小於右子樹中的每一個節點的鍵值。
平衡二叉搜索樹(balance binary search tree)
- 在某些情況下,輸入值不夠隨機,或者經過某些插入或刪除操作,二叉搜索樹可能會失去平衡,造成搜尋效率低下的情況。
- 所謂樹形平衡與否並沒有一個絕對的測量標準.“平衡”的大致意義是:沒有任何一個節點過深。AVL-Tree,RB-Tree均可實現出平衡二叉樹,它們比一般的二叉搜索樹複雜。但是它們可以避免極難應付的最壞情況(高度不平衡)情況,而且由於它們總是保持某種程度的平衡,所以元素的查找時間平均而言也就比較少,一般而言其搜尋時間可節省25%左右。
RB-Tree(紅黑樹)
- 規則
- 每個節點不是紅色就是黑色
- 根節點爲黑色
- 如果節點爲紅,其子節點必須爲黑
- 任一節點至NULL(樹尾端)的任何路徑,所含之黑節點數必須爲黑。
插入節點的情況
X:新節點
P:父節點
S:伯父節點
G:祖父節點
GG:曾祖父節點
- 狀況1 S爲黑且X爲外側插入。PG右旋,更改PG顏色
- 可能產生不平衡狀態(高度相差1)。rb-tree的平衡性本來就比avl-tree弱,然後rb-tree通常能夠導致良好的平衡狀態。經驗告訴我們,rb-tree的搜尋平均效率和avl-tree幾乎相同
- 狀況2 S爲黑且X爲內側插入。PX左旋,更改GX顏色,在對G右旋。
- S爲紅且X爲外側插入。PG右旋,改變X顏色,如果GG爲黑搞定。
一個由上而下的程序
- 假設新增節點爲A,那麼就延着A的路徑,只要看到有某節點X的兩個子節點皆爲紅色,就把X改爲紅色,並把兩個節點改爲黑色。
- 但是如果X的父節點P爲紅色(此時S絕不可能爲紅) ,就得像狀況1一樣,做一次單旋轉並改變顏色,或者想狀況2一樣做雙旋轉,並改變顏色
- 之後插入就很單純了,要麼直接插入,要麼插入後再旋轉
RB-Tree的節點設計
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; 元素值
};
RB-tree的迭代器
- 迭代器基類
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) { 如果有右子節點,則需要找右邊最小的節點 (狀況1)
node = node->right; 就向右走
while (node->left != 0) 然後一直往左走,走到最小,即爲右邊最小節點
node = node->left; 即是答案
}
else { 如果沒有右子節點(狀況2)
base_ptr y = node->parent; 找出父節點
while (node == y->right) { 如果節點本身是父節點的右節點
node = y; 則一直往上直到不是右節點
y = y->parent;
}
if (node->right != y) 如果此時的右節點,不等於父節點
node = y; 此時的父節點即是答案 即,node是子樹的最有右節點,則找到子樹的父節點即爲答案 (狀況3)
否則此時node即爲答案(head)
}
注意,以上判斷「若此時的右子節點不等於此時的父節點」,是爲了應付特殊情況
特殊情況:我們想尋找根節點的下一個節點,此時根節點無右子節點。
需要聯繫root節點與header的特殊關係
}
// 重載--操作符調用
void decrement()
{
if (node->color == __rb_tree_red && 如果是紅節點
node->parent->parent == node) 父節點的父節點等於自己
node = node->right; 右節點即爲答案( 狀況1)
以上情況發生於node爲header時(即 node 爲 end() 時)。
header的右子節點指向整顆樹的max
else if (node->left != 0) { 如果有左節點。即左節點的最大值,左節點之後一直往右走(狀況2)
base_ptr y = node->left; 令y指向左節點
while (y->right != 0) 當y有右節點時
y = y->right; 一直往右走
node = y; 即爲答案
}
else { 即非header節點,也沒有左子節點(子樹的最左邊,最小值)
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; } 返回元素
重載前++,找下個節點
self& operator++() { increment(); return *this; }
重載後++,返回臨時值
self operator++(int) {
self tmp = *this;
increment();
return tmp;
}
重載前--
self& operator--() { decrement(); return *this; }
重載後--
self operator--(int) {
self tmp = *this; 返回臨時值
decrement(); --
return tmp;
}
};
RB-tree的數據結構
- 容器
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; 顏色類型
typedef __rb_tree_iterator<value_type, reference, pointer> iterator; 迭代器
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; // 節點間鍵值大小的比較,仿函數.類的()
}
- 申請節點,釋放節點
protected:
link_type get_node() { return rb_tree_node_allocator::allocate(); } 分配一個節點內存
void put_node(link_type p) { rb_tree_node_allocator::deallocate(p); } 釋放一個節點內存
link_type create_node(const value_type& x) {
link_type tmp = get_node(); 配置一個節點
__STL_TRY{
construct(&tmp->value_field, x); place new,調用數據類型的構造函數(用x構造)
}
__STL_UNWIND(put_node(tmp));
return tmp;
}
void destroy_node(link_type p) {
destroy(&p->value_field); 釋放節點析構元素
put_node(p); 釋放節點內存給內存池
}
}
- 容器初始化
三個函數獲取header
link_type& root() const { return (link_type&)header->parent; } header的父節點爲root
link_type& leftmost() const { return (link_type&)header->left; } header的左節點指向最小值
link_type& rightmost() const { return (link_type&)header->right; } header的右節點指向最大值
void init() {
header = get_node(); 申請一個節點內存
color(header) = __rb_tree_red; 令header爲紅,用於區分root
root() = 0; header的父節點爲空
leftmost() = header; 令 header 的左節點爲自己
rightmost() = header; 令 header 的右節點爲自己。
}
- 常用容器函數實現
iterator begin() { return leftmost(); } 紅黑樹的起點爲最左點,也就是Key值最小的那個節點
重載了--。所以--end()是,header的右子節點,
也就是紅黑樹的最右點 ,符合前閉後開原則,end是最後一個節點的下一個
iterator end() { return header; }
- 插入元素(insert_unique)
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>::
__insert(base_ptr x_, base_ptr y_, const Value& v) {
在x處安插v,父節點爲y
link_type x = (link_type)x_;
link_type y = (link_type)y_;
link_type z;
key_compare 是鍵值大小比較
往左插入
if (y == header || x != 0 || key_compare(KeyOfValue()(v), key(y))) {
z = create_node(v); 產生一個新節點並用v初始化
left(y) = z; 令y的左節點爲x 當y爲header時,leftmost() = z
if (y == header) {
root() = z; header的父節點爲root,z爲root
rightmost() = z; header的右節點也指向root
}
else if (y == leftmost()) 如果父節點爲最左節點
leftmost() = z; 調整leftmost()(header->left),使它永遠指向最小點
}
else { 往右插入
z = create_node(v); 產生一個新節點
right(y) = z; 令新節點成爲父節點的右節點
if (y == rightmost()) 如果父節點是最右節點(最大值)
rightmost() = z; 調整rightmost()(header->right),使它永遠指向最右節點
}
parent(z) = y; 設定新節點的父節點爲z
left(z) = 0; 設定新節點的左子點爲空
right(z) = 0; 設定新節點的右子點爲空
旋轉,跳躍,我閉着眼~
__rb_tree_rebalance(z, header->parent); z爲新增節點,header->parent爲root節點
++node_count; 節點梳理增加
return iterator(z); 用新增的節點構造一個迭代器變量返回
}
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)
{
link_type y = header;
link_type 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節點即爲所安插節點的父節點,必爲節點(第一次插入時,header即爲父節點)
iterator j = iterator(y); 令j等於即將安插節點的父節點
if (comp) 標識是安插在父節點的左側還是右側,comp爲true 安插在左側
if (j == begin()) 如果安插點元素之父節點爲最左節點
x 爲安插點,y 爲安插點之父節點,v爲新值。
return pair<iterator, bool>(__insert(x, y, v), true);
else 如果安插點之父節點不是最左節點
--j; 調整安插點之父節點--
if (key_compare(key(j.node), KeyOfValue()(v)))
v的鍵值大於j.node的鍵值
如果v本來就是要插在y的右側那麼 爲v的鍵值大於y鍵值那麼爲true
如果v本來是要安插在y的左側,那麼一定比--y大,不然父節點不會是y,所以v的鍵值肯定比--y的鍵值大,否則是有相同的鍵值
在x處安插v,父節點爲v
return pair<iterator, bool>(__insert(x, y, v), true);
進行至此,標識新值一定與樹中鍵值重複,那麼就不該插入新值
return pair<iterator, bool>(j, false);
}
紅黑樹旋轉調整
- 旋轉調整
右旋
將x的左子節點的右子節點調整爲x
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的左子節點
x->left = y->right; x的左子節點調整爲y的右子節點
if (y->right != 0)
y->right->parent = x; y的右子節點的父節點調整爲x
y->parent = x->parent; y的父節點調整爲x的父節點
if (x == root) x爲root
root = y; 調整root
else if (x == x->parent->right) x爲父節點的右子節點時
x->parent->right = y; 調整x父節點的右子節點爲x的左子節點
else x爲父節點的左子節點時
x->parent->left = y; 調整x父節點的左子節點爲x的左子節點
y->right = x; y的右節點爲x
x->parent = y; x的父節點爲y
左旋,把x的右子節點的左子節點調整爲x
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的右節點
x->right = y->left; y的Left,右邊的小邊,給到x的右節點
if (y->left != 0)
y->left->parent = x; y的left的父節點賦值爲x
y->parent = x->parent;
if (x == root) x爲根節點時
root = y; 將右節點設置爲root節點
else if (x == x->parent->left) 如果x爲其父節點的左節點
x->parent->left = y; 調整x的父節點的左節點爲x的右節點
else 如果x爲其父節點的右節點
x->parent->right = y; 調整x的父節點的右節點爲x的右節點
y->left = x; y的左節點爲x
x->parent = y; x的父節點爲y
}
- 自上而下的調整
重新令樹形平衡,改變顏色旋轉樹形
參數一爲新增節點,參數2爲root根節點
全局函數
while循環最多執行兩次
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爲伯父節點
第二次while循環時,此時的伯父節點一定爲黑
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; 根節點永遠爲黑
}