STL——关联式容器
一、關聯式容器
標準的STL關聯式容器分為set(集合)/map(映射表)兩大類,以及這兩大類的衍生體multiset(多鍵集合)和 multimap(多鍵映射表)。這些容器的底層機制均以RB-tree(紅黑樹)完成。RB-tree也是一個獨立容器,但并不開放給外界使用。
此外,SGI STL 還提供了一個不在標準規格之列的關聯式容器:hash table (散列表),以及以此hash table 為底層機制而完成的hash_set(散列集合)、hash_map(散列映射表)、hash_multiset(散列多鍵集合)、hash_multimap(散列多鍵映射表)。
所謂的關聯式容器,觀念上類似關聯式數據庫:每筆數據(每個元素)都有一個鍵值(key)和一個實值(value)。當元素被插入到關聯式容器中時,容器內部結構(可能是RB-tree ,也可能是hash-table)便依照其鍵值大小,以某種特定規則將這個元素放置于適當位置。關聯式容器沒有所謂的頭尾(只有最大元素和最小元素),所以不會有所謂push_back()/push_front()/pop_back()/pop_front()/begin()/end() 這樣的操作行為。
一般而言,關聯式容器的內部結構是一個balanced binary tree(平衡二叉樹),以便獲得良好的搜尋效率。balanced binary tree 有許多種類型,包括AVL-tree、RB-tree、AA-tree ,其中最被廣泛運用于STL的是RB-tree(紅黑樹)。
二、樹
有關樹以及紅黑樹的相關內容,參見 紅黑樹詳解 博文。 紅黑樹及其迭代器源碼請參考《STL源碼剖析》5.2節。
三、set
set 的特性是,所有元素都會根據元素的鍵值自動被排序。set 的元素不像map那樣可以同時擁有實值和鍵值,set元素的鍵值就是實值,實值就是鍵值。set不允許兩個元素有相同的鍵值。
務必注意,我們不能通過set的迭代器改變set的元素值。因為set元素值就是其鍵值,關系到set元素的排序規則。如果任意改變set元素值,會嚴重破壞set組織。在set的源碼中,set<T>::iterator被定義為底層RB-tree 的const_iterator,杜絕寫入操作。換句話說,set iterators 是一種constant iterators(相對于mutable iterators)。
STL特別提供了一組set、multiset 相關算法,包括交集set_intersection、并集set_union、差集set_difference、對稱差集set_symmetric_difference。
由于RB-tree 是一種平衡二叉搜索樹,自動排序的效果很不錯,所以標準的STL set即以RB-tree為底層機制。又由于set 所開放的各種操作接口,RB-tree也都提供了,所以幾乎所有的set操作行為,都只是轉調用RB-tree 的操作行為而已。
參見相關源碼;
四、map
map的特性是,所有元素都會根據元素的鍵值自動被排序。map的所有元素都是pair,同時擁有實值和鍵值。pair 的第一元素被視為鍵值,第二元素被視為實值。map不允許兩個元素擁有相同的鍵值(但兩個元素的實值可以相同)。同樣,我們不能通過map的迭代器改變map的鍵值,但我們可以改變其實值。因此,map iterators 既不是一種constant iterators,也不是一種mutable iterators。map也是也是以RB-tree為底層機制,通過轉調RB-tree的操作行為來實現自己的接口功能。
參見相關源碼;
五、multiset
multiset 的特性以及用法和set 完全相同,唯一的差別在于它允許鍵值重復,因此它的插入操作采用的是底層機制RB-tree的insert_equal()而非insert_unique()——insert_equal()表示被插入節點的鍵值在整棵樹中可以重復,因此,無論如何插入都會成功(除非空間不足導致配置失敗)。insert_unique()表示被插入節點的鍵值key在整棵樹中必須獨一無二(因此,如果樹中已存在相同的鍵值,插入操作就不會真正進行)。
參見相關源碼;
六、multimap
multimap 的特性以及用法與map完全相同,唯一的差別在于它允許鍵值重復,因此它的插入操作采用的是底層機制RB-tree的insert_equal()而非insert_unique()。 參見相關源碼;
參見附錄1 SGI STL RB-tree的insert_equal()和insert_unique()插入源碼
七、hashtable
參見 hashtable詳解
八、hash_set
雖然STL 只規范復雜度與接口,并不規范實現方法,但STL set 多半以RB-tree 為底層機制。SGI 則是在STL 標準規格之外另又提供了一個所謂的hash_set,以hashtable 為底層機制。由于hash_set所供應的操作接口,hashtable都提供了,所以幾乎所有的hash_set 操作行為,都只是轉調用hashtable的操作行為而已。
運用set,為的是能夠快速搜尋元素。這一點,不論其底層是RB-tree 或是hashtable,都可以達成任務。但是請注意,RB-tree 有自動排序功能而hashtable 沒有,反映出來的結果就是,set的元素有自動排序功能而hash_set 沒有。 hash_set 的使用方式,與set完全相同。
參見相關源碼;
九、hash_map
SGI 在STL 標準規格之外,另提供了一個所謂的hash_map,以hashtable 為底層機制。由于hash_map 所供應的操作接口,hashtable 都提供了,所以幾乎所有的hash_map操作行為,都只是轉調用hashtable 的操作行為而已。
RB-tree 有自動排序功能而hashtable 沒有,所以map 的元素有自動排序功能而hash_map沒有。
參見相關源碼;
十、hash_multiset
hash_multiset 的特性于multiset 完全相同。唯一的差別在于它的底層機制是hashtable。也因此,hash_multiset 的元素并不會被自動排序。
hash_multiset 和 hash_set 實現上的唯一差別在于,前者的元素插入操作采用底層機制hashtable的insert_equal(),后者則是采用 insert_unique()。
十一、hash_multimap
hash_multimap 的特性于multimap 完全相同。唯一的差別在于它的底層機制是hashtable。也因此,hash_multimap 的元素并不會被自動排序。
hash_multimap 和 hash_map 實現上的唯一差別在于,前者的元素插入操作采用底層機制hashtable的insert_equal(),后者則是采用 insert_unique()。
附錄1:SGI STL RB-tree的insert_equal()和insert_unique()插入源碼
// 插入新值:節點鍵值允許重復 // 注意,返回值是一個RB-Tree迭代器,指向新增節點 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_equal(const _Value& __v) {_Link_type __y = _M_header;_Link_type __x = _M_root(); // 從根節點開始while (__x != 0) { // 從根節點開始,往下尋找適當的插入點__y = __x;// _M_key_compare(v, x) =意思是=> (x > v ? true : false)__x = _M_key_compare(_KeyOfValue()(__v), _S_key(__x)) ? _S_left(__x) : _S_right(__x);// 新值遇到比它“大”的節點則往左,否則(遇到比它“小于或等于”的節點)則往右 }return _M_insert(__x, __y, __v);// 以上,x為新值插入點,y為插入點之父節點,v為新值 }// 插入新值:節點鍵值不允許重復,若重復則插入無效 // 注意,返回值是個pair,第一個元素是個RB-Tree迭代器,指向新增節點, // 第二個元素表示插入成功與否 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 = _M_header;_Link_type __x = _M_root(); // 從根節點開始bool __comp = true;while (__x != 0) { // 從根節點開始,往下尋找適當的插入點__y = __x;__comp = _M_key_compare(_KeyOfValue()(__v), _S_key(__x)); // 如果節點值比新值大則往左;否則往右(注意,_KeyOfValue()(__v)為第1參數)__x = __comp ? _S_left(__x) : _S_right(__x);} //離開while循環之后,__y所指即插入點之父節點(此時的它必為葉節點) iterator __j = iterator(__y); // 令迭代器__j指向插入點之父節點__y if (__comp) // 如果離開while循環時comp為真(表示插入節點值“大于”新值,將在左側插入)if (__j == begin()) // 如果插入點之父節點為最左節點return pair<iterator,bool>(_M_insert(__x, __y, __v), true);// 以上,x為插入點,y為插入點之父節點,v為新值else // 否則(插入點之父節點不為最左節點)--__j; // 調整__j (重新確定插入點,見附錄2圖)// (1)__comp == false => 插入節點值“小于或等于”新值,右側插入// (2)__comp == true && 插入點之父節點不為最左節點 => 調整__j(重新確定插入點,見附錄2圖)if (_M_key_compare(_S_key(__j._M_node), _KeyOfValue()(__v)))// 如果新值“大于”插入節點值((注意,_KeyOfValue()(__v)為第2參數))return pair<iterator,bool>(_M_insert(__x, __y, __v), true);// 以上,__x為新值插入點,__y為插入點之父節點,__v為新值// 至此,表示新值一定與樹中鍵值重復,那么就不該插入新值return pair<iterator,bool>(__j, false); }
附錄2圖:
注:準備插入v=20的值,此時RB-Tree里面已經有一個值為20的節點了。
遍歷步驟已經插入流程如下(如圖,紅色箭頭表示遍歷路徑):
1. 40節點比新值大,往左走;
2. 20節點不比新值大(“小于或等于”),往右走;
3. 30節點比新值大,往左走;同時,達到葉子節點;此時j指向30節點;
4. 30節點不是最左節點,執行“--j”,j新指向20節點;
5. 執行后續判斷(也即進入上面代碼(1)、(2)情況的語句):
a. 新值是否大于j節點,如果是,插入;
b. 否則(本例子的情況,“小于或等于”),不插入。
注:步驟2(20節點“小于或等于”新值)和步驟5(新值“小于或等于”20節點),由這兩個判斷可以推斷:新值“等于”20節點。進而說明了原RB-Tree中已有相同的值,故而不再插入。?
?
轉載于:https://www.cnblogs.com/yyxt/p/4985239.html
總結
以上是生活随笔為你收集整理的STL——关联式容器的全部內容,希望文章能夠幫你解決所遇到的問題。

- 上一篇: 临时冻结不得超过多少小时
- 下一篇: 人工智能专业好考部队文职吗