stl_tree.h 源碼剖析

<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;	根節點永遠爲黑
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章