1. 程式人生 > >C++ 學習筆記:STL 容器一些底層機制

C++ 學習筆記:STL 容器一些底層機制

1、vector 容器

vector 的資料安排以及操作方式,與 array 非常相似。兩者的唯一區別在於空間的運用的靈活性。array 是靜態空間,一旦配置了就不能改變,vector 是動態陣列。在堆上分配空間。vector 是動態空間,隨著元素的加入,它的內部機制會自行擴充空間以容納新元素(有保留記憶體,如果減少大小後記憶體也不會釋放。如果新值>當前大小時才會再分配記憶體,這大大影響了 vector 的效率,)。因此,vector 的運用對於記憶體的合理利用與運用的靈活性有很大的幫助,我們再也不必因為害怕空間不足而一開始要求一個大塊的 array。

vector 動態增加大小,並不是在原空間之後持續新空間(因為無法保證原空間之後尚有可供配置的空間),而是以原大小的兩倍另外配置一塊較大的空間,然後將原內容拷貝過來,然後才開始在原內容之後構造新元素,並釋放原空間。因此,對 vector 的任何操作,一旦引起空間重新配置,同時

指向原vector 的所有迭代器就都失效了。

對最後元素操作最快(在後面新增刪除最快),此時一般不需要移動記憶體。對中間和開始處進行新增刪除元素操作需要移動記憶體。如果你的元素是結構或是類,那麼移動的同時還會進行構造和析構操作,所以效能不高(最好將結構或類的指標放入 vector 中,而不是結構或類本身,這樣可以避免移動時的構造與析構)。訪問方面,對任何元素的訪問都是 O(1),也就是常數時間的。 

總結:

 vector 常用來儲存需要經常進行隨機訪問的內容並且不需要經常對中間元素進行新增刪除操作


2、list 容器

相對於 vector 的連續空間,list 就顯得複雜許多,它的好處是每次插入或刪除一個元素,就配置或釋放一個元素空間,元素也是在堆中。因此,list 對於空間的運用有絕對的精準,一點也不浪費。而且,對於任何位置的元素插入或元素移除,永遠是常數時間。STL 中的list 底層是一個雙向連結串列

而且是一個環狀雙向連結串列。這個特點使得它的隨即存取變的非常沒有效率,因此它沒有提供 [] 操作符的過載

總結:

如果你喜歡經常新增刪除大物件的話,那麼請使用 list;
要儲存的物件不大,構造與析構操作不復雜,那麼可以使用 vector 代替。
list<指標> 完全是效能最低的做法,這種情況下還是使用 vector<指標> 好,因為指標沒有構造與析構,也不佔用很大記憶體


3、deque 容器

deque 是一種雙向開口的連續線性空間,元素也是在堆中。所謂雙向開口,意思是可以在隊尾兩端分別做元素的插入和刪除操作。deque 和 vector 的最大差異,一在於 deque 允許於常數時間內對起頭端進行元素的插入或移除操作,二在於deque沒有所謂容量觀念,因為它是動態地以分段連續空間組合而成
,隨時可以增加一段新的空間並連結在一起。換句話說,像 vector 那樣“因舊空間不足而重新配置一塊更大空間,然後複製元素,再釋放舊空間”這樣的事情在 deque 是不會發生的。它的儲存形式如下:
[堆1] --> [堆2] -->[堆3] --> ...
deque 是由一段一段的定量連續空間構成。一旦有必要在 deque 的前端或尾端增加新空間,便配置一段定量連續空間,串接在整個 deque 的頭端或尾端。deque 的最大任務,便是在這些分段的定量連續空間上,維護其整體連續的假象,並提供隨機存取的介面。避開了“重新配置,複製,釋放”的輪迴,代價則是複雜的迭代器架構。因為有分段連續線性空間,就必須有中央控制器,而為了維持整體連續的假象,資料結構的設計及迭代器前進後退等操作都頗為繁瑣。

deque 採用一塊所謂的 map 作為主控。這裡的 map 是一小塊連續空間,其中每個元素都是指標,指向另一段連續線性空間,稱為緩衝區。緩衝區才是 deque 的儲存空間主體。( 底層資料結構為一箇中央控制器和多個緩衝區)SGI STL 允許我們指定緩衝區大小,預設值 0 表示將使用 512 bytes 緩衝區。

支援[]操作符,也就是支援隨即存取,可以在前面快速地新增刪除元素,或是在後面快速地新增刪除元素,然後還可以有比較高的隨機訪問速度和vector 的效率相差無幾。deque 支援在兩端的操作:push_back,push_front,pop_back,pop_front等,並且在兩端操作上與 list 的效率也差不多。
在標準庫中 vector 和 deque 提供幾乎相同的介面,在結構上區別主要在於在組織記憶體上不一樣,deque 是按頁或塊來分配儲存器的,每頁包含固定數目的元素;相反 vector 分配一段連續的記憶體,vector 只是在序列的尾段插入元素時才有效率,而 deque 的分頁組織方式即使在容器的前端也可以提供常數時間的 insert 和 erase 操作,而且在體積增長方面也比 vector 更具有效率。

總結

vector 是可以快速地在最後新增刪除元素,並可以快速地訪問任意元素;
list 是可以快速地在所有地方新增刪除元素,但是隻能快速地訪問最開始與最後的元素;

deque 在開始和最後新增元素都一樣快,並提供了隨機訪問方法,像vector一樣使用 [] 訪問任意元素,但是隨機訪問速度比不上vector快,因為它要內部處理堆跳轉。deque 也有保留空間。另外,由於 deque 不要求連續空間,所以可以儲存的元素比 vector 更大,這點也要注意一下。還有就是在前面和後面新增元素時都不需要移動其它塊的元素,所以效能也很高。

因此在實際使用時,如何選擇這三個容器中哪一個,應根據你的需要而定,一般應遵循下面的原則:

1、如果你需要高效的隨即存取,而不在乎插入和刪除的效率,使用 vector;
2、如果你需要大量的插入和刪除,而不關心隨即存取,則應使用 list;

3、如果你需要隨即存取,而且關心兩端資料的插入和刪除,則應使用deque。


4、stack

stack 是一種先進後出(First In Last Out , FILO)的資料結構。它只有一個出口,stack 允許新增元素,移除元素,取得最頂端元素。但除了最頂端外,沒有任何其它方法可以存取stack的其它元素,stack不允許遍歷行為。
以某種容器( 一般用 list 或 deque 實現,封閉頭部即可,不用 vector 的原因應該是容量大小有限制,擴容耗時)作為底部結構,將其介面改變,使之符合“先進後出”的特性,形成一個 stack,是很容易做到的。deque 是雙向開口的資料結構,若以 deque 為底部結構並封閉其頭端開口,便輕而易舉地形成了一個stack。因此,SGI STL 便以 deque 作為預設情況下的 stack 底部結構,由於 stack 系以底部容器完成其所有工作,而具有這種“修改某物介面,形成另一種風貌”之性質者,稱為 adapter(配接器),因此,STL stack 往往不被歸類為 container(容器),而被歸類為 container adapter。

5、 queue

queue 是一種先進先出(First In First Out,FIFO)的資料結構。它有兩個出口,queue 允許新增元素,移除元素,從最底端加入元素,取得最頂端元素。但除了最底端可以加入,最頂端可以取出外,沒有任何其它方法可以存取 queue 的其它元素。
以某種容器 一般用 list 或 deque 實現,封閉頭部即可,不用 vector 的原因應該是容量大小有限制,擴容耗時作為底部結構,將其介面改變,使之符合“先進先出”的特性,形成一個 queue,是很容易做到的。deque 是雙向開口的資料結構,若以 deque 為底部結構並封閉其底部的出口和前端的入口,便輕而易舉地形成了一個 queue。因此,SGI STL 便以 deque 作為預設情況下的 queue 底部結構,由於 queue 系以底部容器完成其所有工作,而具有這種“修改某物介面,形成另一種風貌”之性質者,稱為 adapter(配接器),因此,STL queue 往往不被歸類為container(容器),而被歸類為 container adapter。 stack 和 queue 其實是介面卡,而不叫容器,因為是對容器的再封裝。

6、heap

heap 並不歸屬於 STL 容器元件,它是個幕後英雄,扮演 priority queue(優先佇列)的助手。priority queue 允許使用者以任何次序將任何元素推入容器中,但取出時一定按從優先權最高的元素開始取。按照元素的排列方式,heap 可分為 max-heap 和 min-heap 兩種,前者每個節點的鍵值(key)都大於或等於其子節點鍵值,後者的每個節點鍵值(key)都小於或等於其子節點鍵值。因此, max-heap 的最大值在根節點,並總是位於底層array或vector的起頭處;min-heap 的最小值在根節點,亦總是位於底層array或vector起頭處。STL 供應的是 max-heap,用 C++ 實現。
堆排序 C++ 語言實現:點選這裡

7、priority_queue

priority_queue 是一個擁有權值觀念的 queue,它允許加入新元素,移除舊元素,審視元素值等功能。由於這是一個 queue,所以只允許在底端加入元素,並從頂端取出元素,除此之外別無其它存取元素的途徑。priority_queue 帶有權值觀念,其內的元素並非依照被推入的次序排列,而是自動依照元素的權值排列(通常權值以實值表示)。權值最高者,排在最前面。預設情況下 priority_queue 系利用一個 max-heap 完成,後者是一個以vector 表現的 complete binary tree.max-heap 可以滿足 priority_queue 所需要的“依權值高低自動遞減排序”的特性。
priority_queue 完全以底部容器一般為vector為底層容器作為根據,再加上 heap 處理規則,所以其實現非常簡單。預設情況下是以 vector 為底部容器。queue 以底部容器完成其所有工作。具有這種“修改某物介面,形成另一種風貌“”之性質者,稱為 adapter(配接器),因此,STL priority_queue 往往不被歸類為 container(容器),而被歸類為 container adapter。

8、set 和 multiset 容器

set 的特性是,所有元素都會根據元素的鍵值自動被排序。set 的元素不像 map 那樣可以同時擁有實值(value)和鍵值(key),set 元素的鍵值就是實值,實值就是鍵值,set不允許兩個元素有相同的值set 底層是通過紅黑樹(RB-tree)來實現的,由於紅黑樹是一種平衡二叉搜尋樹,自動排序的效果很不錯,所以標準的 STL 的 set 即以 RB-Tree 為底層機制。又由於 set 所開放的各種操作介面,RB-tree 也都提供了,所以幾乎所有的 set 操作行為,都只有轉呼叫 RB-tree 的操作行為而已。

multiset的特性以及用法和 set 完全相同,唯一的差別在於它允許鍵值重複,因此它的插入操作採用的是底層機制是 RB-tree 的 insert_equal() 而非 insert_unique()。

9、map 和 multimap 容器

map的特性是,所有元素都會根據元素的鍵值自動被排序。map 的所有元素都是 pair,同時擁有實值(value)和鍵值(key)。pair 的第一元素被視為鍵值,第二元素被視為實值。map不允許兩個元素擁有相同的鍵值。由於 RB-tree 是一種平衡二叉搜尋樹,自動排序的效果很不錯,所以標準的STL map 即以 RB-tree 為底層機制。又由於 map 所開放的各種操作介面,RB-tree 也都提供了,所以幾乎所有的 map 操作行為,都只是轉調 RB-tree 的操作行為。

multimap 的特性以及用法與 map 完全相同,唯一的差別在於它允許鍵值重複,因此它的插入操作採用的是底層機制 RB-tree 的 insert_equal() 而非 insert_unique。 

10、hash_set 和 hash_multiset 容器

hash_set 底層資料結構為 hash 表,無序,不重複。

hash_multiset 底層資料結構為 hash 表,無序,不重複。 

11、hash_map 和 hash_multimap 容器

hash_map 底層資料結構為 hash 表,無序,不重複。

hash_multimap 底層資料結構為 hash 表,無序,不重複。 

參考這個地方這個地方還有這個地方