關于STL與泛型編程學習感想三(博覽網)

體系結構與內核分析續

deque&queue 和 stack 深度探索

deque雙向隊列是一種雙向開口的連續線性空間,可以高效的在頭尾兩端插入和刪除元素,deque在接口上和vector非常相似。(連續是假象,分段是事實)

deque的內存空間分布是小片的連續,小片間用鏈表相連,實際上內部有一個map的指針。其中buffer表示deque的緩沖區,每個buffer可以存放多個元素結點。并且每個buffer指針存放在vector容器中。每個iterator存放四個指針:cur、first、last、node,cur表示當前數據結點的指針,first表示buffer首指針,last表示buffer的尾指針,node表示這個iterator在vector中的指針地址。所有容器都有兩個iterator分別指向頭和尾begin& finish。

deque用法:http://blog.csdn.net/hnust_xiehonghao/article/details/8800007



源代碼示例:deque::insert()

//在position處安插一個元素,其值為x

iterator insert(iterator position,const value_type& x){

if(position.cur == start.cur){? ? ? ? ? ? ? //如果安插點是deque最前端

push_front(x);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //交給push——front()做

return start;

}

else if (position.cur == finish.cur) {? ? //如果安插點是deque最尾端

push_back(x);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //交給push——back()做

iterator tmp =finish;

--tmp;

return tmp;

}

else {

return insert_aux(position,x);

}

}

template

typename deque::iterator

deque::insert_aux(iterator pos,const value_type& x){

difference_type index = pos - start;? ? //安插點之前的元素個數

value_type x_copy =x;

if (index

push_front(front());? ? ? ? ? ? //在最前端加入與第一元素同值的元素

...

copy(front2,pos1,front1);? ? //元素搬移

}

else{? ? ? ? ? ? ? ? ? ? ? ? //安插點之后的元素個數較少

push_back(back());? //在尾端加入與最末元素同值的元素

...

copy_backward(pos,back2,back1);? ? ? //元素搬移

}

*pos = x_copy;? ? ? ? //在安插點上設定新值

return pos;

}

deque是如何模擬連續空間:源代碼做了大量的操作符重載,尤其是遇到buffer邊界時如何跳到控制中心,使得deque的iterator能夠在buffer之間模擬出連續空間。iterator的功勞特別是大量的操作符重載。

實現源代碼如下:

reference operator[] (size_type n)

{

return start[difference_type(n)];

}

reference front(){return *start;}

reference back()

{

iterator tmp = finish;

--tmp;

return *tmp;

}

size_type size() const {return finish - start;}

bool empty() const {return == start;}

reference operator*() const {return *cur;}

pointer operator->() const {return & (operator*());}

queue(先進先出)和stack(先進后出):內含一個deque封掉某些功能。

stack和queue都不允許遍歷,也不提供iterator。

stack和queue都可選擇list和deque作為底層容器。

queue不可選擇vector作為底層結構,stack可選擇vector作為底層結構。

stack和queue都不可選擇set或map做底層結構。

RB-tree深度探索

Red-Black tree(紅黑樹)是平衡二元搜尋樹(balanced binary search tree)中常被使用的一種。平衡二元搜尋樹的特征:排列規則有利于search和insert,并保持適度平衡,即無任何節點過深。

紅黑樹的性質如下:

1) 每一個節點或者著紅色,或者著成黑色.

2) 根是黑色的

3) 如果一個節點是紅色的,那么它的子節點必須是黑色的.

4) 從一個節點到一個NULL指針的每一條路徑必須包含相同數目的黑色節點.



re_tree提供“遍歷”操作及iterators。按正常規則(++ite)遍歷,便能獲得排序狀態(sorted)。我們不應使用rb_tree的iterators改變元素值(因為元素有其嚴謹排列規則)。編程并未阻止此事。如此設計是正確的,因為rb_tree即將為set和map服務(作為其底部支持),而map允許元素的data被改變,只有元素的key才是不可被改變的。key和data一起才為value。rb_tree提供兩種insertion操作:insert_unique()和insert_equal( )。前者表示節點的key一定在整個tree中獨一無二,否則安插失敗;后者表示節點的key可重復。

RB-tree的用法:

begin()函數獲取最左(最小)的節點處,end()獲取的是根結點(由于使用了一個實現上的技巧,其實并不是真的根結點);

empty()判斷是否為空, size(), max_size()最大數據的數量,count();

insert_unique(const value_type& x)將x插入RB-tree中,保持節點值獨一無二;

insert_equal(const value_type& x)將x插入RB-tree中,允許節點值重復。

關聯式容器

set/multiset(set中key不可重復,multiset可以|后面map相同) (key就是vault)二分樹(高度平衡)map/multimap(有key和vault)(所有元素都會根據元素的鍵值自動被排序)在STL中關聯容器使用紅黑樹來實現,因為不是順序結構,因而不能使用上面提到的push和pop函數,使用insert和erase函數來實現元素的插入刪除操作。關聯容器支持通過鍵來高效地查找和讀取元素,兩個基本的關聯容器類型是map和set。map的元素以鍵-值(key-value)對的形式組織:鍵用于元素在map中的索引,而值則表示所存儲和讀取的數據。set僅包含一個鍵,并有效地支持關于某個鍵是否存在的查詢。map可理解為字典,set可理解為一類元素的集合。關聯容器和順序容器的本質差別在于:關聯容器通過鍵(key)存儲和讀取元素,而順序容器則通過元素在容器中的位置順序存儲和訪問元素。set 和 map 類型的對象所包含的元素都具有不同的鍵,不允許為同一個鍵添加第二個元素。如果一個鍵必須對應多個實例,則需使用 multimap 或 multi set,這兩種類型允許多個元素擁有相同的鍵。

set/multiset深度探索

set/multiset以rb_tree為底層結構(內含一個tree),因此有元素自動排序特性。排序的依據是key,而set/multiset元素的value和key合一:value就是key。

set/multiset提供“遍歷”操作及iterators。按正常規則(++ite)遍歷,便能獲得排序狀態(sorted)。

我們無法使用set/multiset的iterators改變元素值(因為key有其嚴謹排列規則)。set/multiset的iterator是其底部的RB tree的const-iterator,就是為了禁止user對元素賦值。

set元素的key必須獨一無二,因此其insert()用的是rb_tree的insert_unique()。multiset元素的key可以重復,因此其insert()用的rb_tree的insert_equal()。

map/multimap深度探索

map/multimap以rb_tree為底層結構,因此有元素自動排序特性。排序的依據是key。

map/multimap提供“遍歷”操作及iterators。按正常規則(++ite)遍歷,便能獲得排序狀態(sorted)。我們無法使用map/multimap的iterators改變元素的key(因為key有其嚴謹排列順序),但可以用它來改變元素的data。因此map/multimap內部自動將user指定的key type設為const,如此便能禁止user對元素的key賦值。(內含tree限制某些功能)

map元素的key必須獨一無二,因此其insert()用的是rb_tree的insert_unique()。multimap元素的key可以重復,因此其insert()用的是rb_tree的insert_equal()。

hashtable深度探索

Hashtable是非泛型的集合,所以在檢索和存儲值類型時通常會發生裝箱與拆箱的操作。

當把某個元素添加到 Hashtable 時,將根據鍵的哈希代碼將該元素放入存儲桶中,由于是散列算法所以會出現一個哈希函數能夠為兩個不同的鍵生成相同的哈希代碼,該鍵的后續查找將使用鍵的哈希代碼只在一個特定存儲桶中搜索,這將大大減少為查找一個元素所需的鍵比較的次數。

Hashtable 的加載因子確定元素與Hashtable 可擁有的元素數的最大比率。加載因子越小,平均查找速度越快,但消耗的內存也增加。默認的加載因子 0.72通常提供速度和大小之間的最佳平衡。當創建 Hashtable 時,也可以指定其他加載因子。(元素總量/ Hashtable 可擁有的元素數=加載因子 )當向 Hashtable 添加元素時,Hashtable 的實際加載因子將增加。當實際加載因子達到指定的加載因子時,Hashtable 中存儲桶的數目自動增加到大于當前 Hashtable 存儲桶數兩倍的最小素數。(Hashtable適用于讀取操作頻繁,寫入操作很少的操作類型)

當碰撞的元素超過“籃子”的個數,經驗判斷為危險,則將“籃子”的數量擴大為原來的兩倍,一般籃子數量是素數。GUC初始籃子數量為53,兩倍變成106,附近的素數而且比106大的為193。可以使用hashtable iterators改變元素的data,但不能改變元素的key(因為hashtable根據key實現嚴謹的元素排列)。

hashtable源代碼:



template六個模板參數:value和key參數與紅黑樹為底層的set和map類似,第三個模板參數hashFcn(hash-function)的目的是希望根據元素值算出一個hash code(一個可進行modulus運算的值),使得元素hash code映射之后能夠夠雜亂夠隨機地被置于hashtable內,越是亂,越是不容易碰撞,可以傳函數、仿函數、函數對象,它是通過模板偏特化實現,c風格的字符串char*是一個指針,stl提供了實現方式,而C++的字符串string則需要自己寫實現方式;第四個ExtractKey告訴我們如何取出key;第五個EqualKey告訴我們如何比較key大小;第六個alloc為分配器。

hash_map和map的區別在哪里?

構造函數? hash_map需要hash函數,等于函數;map只需要比較函數(小于函數).

存儲結構? hash_map采用hash表存儲,map一般采用紅黑樹(RB Tree)實現。因此其memory數據結構是不一樣的。

unordered容器概念

Before C++11

hash_set

hash_multiset

hash_map

hash_multimap

Since C++11

unordered_set

unordered_multiset

unordered_map

unordered_multimap

只是名稱不同,用法不變

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容