轉載自:blog.csdn.net/acosoft算法
什麼是容器後端
首先,咱們必須理解一下什麼是容器,在C++ 中容器被定義爲:在數據存儲上,有一種對象類型,它能夠持有其它對象或指向其它對像的指針,這種對象類型就叫作容器。很簡單,容器就是保存其它對象的對象,固然這是一個樸素的理解,這種「對象」還包含了一系列處理「其它對象」的方法,由於這些方法在程序的設計上會常常被用到,因此容器也體現了一個好處,就是「容器類是一種對特定代碼重用問題的良好的解決方案」。數組
容器還有另外一個特色是容器能夠自行擴展。在解決問題時咱們經常不知道咱們須要存儲多少個對象,也就是說咱們不知道應該建立多大的內存空間來保存咱們的對象。顯然,數組在這一方面也力不從心。容器的優點就在這裏,它不須要你預先告訴它你要存儲多少對象,只要你建立一個容器對象,併合理的調用它所提供的方法,全部的處理細節將由容器來自身完成。它能夠爲你申請內存或釋放內存,而且用最優的算法來執行您的命令。數據結構
容器是隨着面嚮對象語言的誕生而提出的,容器類在面嚮對象語言中特別重要,甚至它被認爲是早期面嚮對象語言的基礎。在如今幾乎全部的面向對象的語言中也都伴隨着一個容器集,在C++ 中,就是標準模板庫(STL )。函數
和其它語言不同,C++ 中處理容器是採用基於模板的方式。標準C++ 庫中的容器提供了多種數據結構,這些數據結構能夠與標準算法一塊兒很好的工做,這爲咱們的軟件開發提供了良好的支持!性能
通用容器的分類優化
STL 對定義的通用容器分三類:順序性容器、關聯式容器和容器適配器。spa
順序性容器 是一種各元素之間有順序關係的線性表,是一種線性結構的可序羣集。順序性容器中的每一個元素均有固定的位置,除非用刪除或插入的操做改變這個位置。這個位置和元素自己無關,而和操做的時間和地點有關,順序性容器不會根據元素的特色排序而是直接保存了元素操做時的邏輯順序。好比咱們一次性對一個順序性容器追加三個元素,這三個元素在容器中的相對位置和追加時的邏輯次序是一致的。.net
關聯式容器 和順序性容器不同,關聯式容器是非線性的樹結構,更準確的說是二叉樹結構。各元素之間沒有嚴格的物理上的順序關係,也就是說元素在容器中並無保存元素置入容器時的邏輯順序。可是關聯式容器提供了另外一種根據元素特色排序的功能,這樣迭代器就能根據元素的特色「順序地」獲取元素。設計
關聯式容器另外一個顯著的特色是它是以鍵值的方式來保存數據,就是說它能把關鍵字和值關聯起來保存,而順序性容器只能保存一種(能夠認爲它只保存關鍵字,也能夠認爲它只保存值)。這在下面具體的容器類中能夠說明這一點。
容器適配器 是一個比較抽象的概念, C++的解釋是:適配器是使一事物的行爲相似於另外一事物的行爲的一種機制。容器適配器是讓一種已存在的容器類型採用另外一種不一樣的抽象類型的工做方式來實現的一種機制。其實僅是發生了接口轉換。那麼你能夠把它理解爲容器的容器,它實質仍是一個容器,只是他不依賴於具體的標準容器類型,能夠理解是容器的模版。或者把它理解爲容器的接口,而適配器具體採用哪一種容器類型去實現,在定義適配器的時候能夠由你決定。
下表列出STL 定義的三類容器所包含的具體容器類:
標準容器類 |
特色 |
順序性容器 |
|
vector |
從後面快速的插入與刪除,直接訪問任何元素 |
deque |
從前面或後面快速的插入與刪除,直接訪問任何元素 |
list |
雙鏈表,從任何地方快速插入與刪除 |
關聯容器 |
|
set |
快速查找,不容許重複值 |
multiset |
快速查找,容許重複值 |
map |
一對多映射,基於關鍵字快速查找,不容許重複值 |
multimap |
一對多映射,基於關鍵字快速查找,容許重複值 |
容器適配器 |
|
stack |
後進先出 |
queue |
先進先出 |
priority_queue |
最高優先級元素老是第一個出列 |
順序性容器
向量 vector :
是一個線性順序結構。至關於數組,但其大小能夠不預先指定,而且自動擴展。它能夠像數組同樣被操做,因爲它的特性咱們徹底能夠將vector 看做動態數組。
在建立一個vector 後,它會自動在內存中分配一塊連續的內存空間進行數據存儲,初始的空間大小能夠預先指定也能夠由vector 默認指定,這個大小即capacity ()函數的返回值。當存儲的數據超過度配的空間時vector 會從新分配一塊內存塊,但這樣的分配是很耗時的,在從新分配空間時它會作這樣的動做:
首先,vector 會申請一塊更大的內存塊;
而後,將原來的數據拷貝到新的內存塊中;
其次,銷燬掉原內存塊中的對象(調用對象的析構函數);
最後,將原來的內存空間釋放掉。
若是vector 保存的數據量很大時,這樣的操做必定會致使糟糕的性能(這也是vector 被設計成比較容易拷貝的值類型的緣由)。因此說vector 不是在什麼狀況下性能都好,只有在預先知道它大小的狀況下vector 的性能纔是最優的。
vector 的特色:
(1) 指定一塊如同數組同樣的連續存儲,但空間能夠動態擴展。即它能夠像數組同樣操做,而且能夠進行動態操做。一般體如今push_back() pop_back() 。
(2) 隨機訪問方便,它像數組同樣被訪問,即支持[ ] 操做符和vector.at()
(3) 節省空間,由於它是連續存儲,在存儲數據的區域都是沒有被浪費的,可是要明確一點vector 大多狀況下並非滿存的,在未存儲的區域實際是浪費的。
(4) 在內部進行插入、刪除操做效率很是低,這樣的操做基本上是被禁止的。Vector 被設計成只能在後端進行追加和刪除操做,其緣由是vector 內部的實現是按照順序表的原理。
(5) 只能在vector 的最後進行push 和pop ,不能在vector 的頭進行push 和pop 。
(6) 當動態添加的數據超過vector 默認分配的大小時要進行內存的從新分配、拷貝與釋放,這個操做很是消耗性能。 因此要vector 達到最優的性能,最好在建立vector 時就指定其空間大小。
雙向鏈表list
是一個線性鏈表結構,它的數據由若干個節點構成,每個節點都包括一個信息塊(即實際存儲的數據)、一個前驅指針和一個後驅指針。它無需分配指定的內存大小且能夠任意伸縮,這是由於它存儲在非連續的內存空間中,而且由指針將有序的元素連接起來。
因爲其結構的緣由,list 隨機檢索的性能很是的很差,由於它不像vector 那樣直接找到元素的地址,而是要從頭一個一個的順序查找,這樣目標元素越靠後,它的檢索時間就越長。檢索時間與目標元素的位置成正比。
雖然隨機檢索的速度不夠快,可是它能夠迅速地在任何節點進行插入和刪除操做。由於list 的每一個節點保存着它在鏈表中的位置,插入或刪除一個元素僅對最多三個元素有所影響,不像vector 會對操做點以後的全部元素的存儲地址都有所影響,這一點是vector 不可比擬的。
list 的特色:
(1) 不使用連續的內存空間這樣能夠隨意地進行動態操做;
(2) 能夠在內部任何位置快速地插入或刪除,固然也能夠在兩端進行push 和pop 。
(3) 不能進行內部的隨機訪問,即不支持[ ] 操做符和vector.at() ;
(4) 相對於verctor 佔用更多的內存。
雙端隊列deque
是一種優化了的、對序列兩端元素進行添加和刪除操做的基本序列容器。它容許較爲快速地隨機訪問,但它不像vector 把全部的對象保存在一塊連續的內存塊,而是採用多個連續的存儲塊,而且在一個映射結構中保存對這些塊及其順序的跟蹤。向deque 兩端添加或刪除元素的開銷很小。它不須要從新分配空間,因此向末端增長元素比vector 更有效。
實際上,deque 是對vector 和list 優缺點的結合,它是處於二者之間的一種容器。
deque 的特色:
(1) 隨機訪問方便,即支持[ ] 操做符和vector.at() ,但性能沒有vector 好;
(2) 能夠在內部進行插入和刪除操做,但性能不及list ;
(3) 能夠在兩端進行push 、pop ;
三者的比較
下圖描述了vector 、list 、deque 在內存結構上的特色:
vector 是一段連續的內存塊,而deque 是多個連續的內存塊, list 是全部數據元素分開保存,能夠是任何兩個元素沒有連續。
vector 的查詢性能最好,而且在末端增長數據也很好,除非它從新申請內存段;適合高效地隨機存儲。
list 是一個鏈表,任何一個元素均可以是不連續的,但它都有兩個指向上一元素和下一元素的指針。因此它對插入、刪除元素性能是最好的,而查詢性能很是差;適合 大量地插入和刪除操做而不關心隨機存取的需求。
deque 是介於二者之間,它兼顧了數組和鏈表的優勢,它是分塊的鏈表和多個數組的聯合。因此它有被list 好的查詢性能,有被vector 好的插入、刪除性能。 若是你須要隨即存取又關心兩端數據的插入和刪除,那麼deque 是最佳之選。
關聯容器
set, multiset, map, multimap 是一種非線性的樹結構,具體的說採用的是一種比較高效的特殊的平衡檢索二叉樹—— 紅黑樹結構。(至於什麼是紅黑樹,我也不太理解,只能理解到它是一種二叉樹結構)
由於關聯容器的這四種容器類都使用同一原理,因此他們核心的算法是一致的,可是它們在應用上又有一些差異,先描述一下它們之間的差異。
set ,又稱集合,實際上就是一組元素的集合,但其中所包含的元素的值是惟一的,且是按必定順序排列的,集合中的每一個元素被稱做集合中的實例。由於其內部是經過鏈表的方式來組織,因此在插入的時候比vector 快,但在查找和末尾添加上被vector 慢。
multiset ,是多重集合,其實現方式和set 是類似的,只是它不要求集合中的元素是惟一的,也就是說集合中的同一個元素能夠出現屢次。
map ,提供一種「鍵- 值」關係的一對一的數據存儲能力。其「鍵」在容器中不可重複,且按必定順序排列(其實咱們能夠將set 也當作是一種鍵- 值關係的存儲,只是它只有鍵沒有值。它是map 的一種特殊形式)。因爲其是按鏈表的方式存儲,它也繼承了鏈表的優缺點。
multimap , 和map 的原理基本類似,它容許「鍵」在容器中能夠不惟一。
關聯容器的特色是明顯的,相對於順序容器,有如下幾個主要特色:
1, 其內部實現是採用非線性的二叉樹結構,具體的說是紅黑樹的結構原理實現的;
2, set 和map 保證了元素的惟一性,mulset 和mulmap 擴展了這一屬性,能夠容許元素不惟一;
3, 元素是有序的集合,默認在插入的時候按升序排列。
基於以上特色,
1, 關聯容器對元素的插入和刪除操做比vector 要快,由於vector 是順序存儲,而關聯容器是鏈式存儲;比list 要慢,是由於即便它們同是鏈式結構,但list 是線性的,而關聯容器是二叉樹結構,其改變一個元素涉及到其它元素的變更比list 要多,而且它是排序的,每次插入和刪除都須要對元素從新排序;
2, 關聯容器對元素的檢索操做比vector 慢,可是比list 要快不少。vector 是順序的連續存儲,固然是比不上的,但相對鏈式的list 要快不少是由於list 是逐個搜索,它搜索的時間是跟容器的大小成正比,而關聯容器 查找的複雜度基本是Log(N) ,好比若是有1000 個記錄,最多查找10 次,1,000,000 個記錄,最多查找20 次。容器越大,關聯容器相對list 的優越性就越能體現;
3, 在使用上set 區別於vector,deque,list 的最大特色就是set 是內部排序的,這在查詢上雖然遜色於vector ,可是卻大大的強於list 。
4, 在使用上map 的功能是不可取代的,它保存了「鍵- 值」關係的數據,而這種鍵值關係採用了類數組的方式。數組是用數字類型的下標來索引元素的位置,而map 是用字符型關鍵字來索引元素的位置。在使用上map 也提供了一種類數組操做的方式,即它能夠經過下標來檢索數據,這是其餘容器作不到的,固然也包括set 。(STL 中只有vector 和map 能夠經過類數組的方式操做元素,即如同ele[1] 方式)
容器適配器
STL 中包含三種適配器:棧stack 、隊列queue 和優先級priority_queue 。
適配器是容器的接口,它自己不能直接保存元素,它保存元素的機制是調用另外一種順序容器去實現,便可以把適配器看做「它保存一個容器,這個容器再保存全部元素」。
STL 中提供的三種適配器能夠由某一種順序容器去實現。默認下stack 和queue 基於deque 容器實現,priority_queue 則基於vector 容器實現。固然在建立一個適配器時也能夠指定具體的實現容器,建立適配器時在第二個參數上指定具體的順序容器能夠覆蓋適配器的默認實現。
因爲適配器的特色,一個適配器不是能夠由任一個順序容器均可以實現的。
棧stack 的特色是後進先出,因此它關聯的基本容器能夠是任意一種順序容器,由於這些容器類型結構均可以提供棧的操做有求,它們都提供了push_back 、pop_back 和back 操做;
隊列queue 的特色是先進先出,適配器要求其關聯的基礎容器必須提供pop_front 操做,所以其不能創建在vector 容器上;
優先級隊列priority_queue 適配器要求提供隨機訪問功能,所以不能創建在list 容器上。