Hints:在本文中,重點總結的是各個容器中增刪查的演算法時間複雜度。
在c++中,容器指的是能夠容納各種資料型別的通用資料資料結構,是類别範本。 比如set類别範本:
template <class _Key, class _Compare = less<_Key>,
class _Allocator = allocator<_Key> >
class _LIBCPP_TYPE_VIS_ONLY set
複製程式碼
C++的容器主要分為三種:
- 順序容器
- 關聯容器
- 容器介面卡
而訪問容器元素我們需要迭代器(引入容器標頭檔案就可以了,不需要單獨因為標頭檔案),迭代器有以下幾個屬性:
- 用於指向順序容器和關聯容器的元素
- 用法和指標類似
- 有const(容器類名::iterator 變數名)和非const兩種(容器類名::const_iterator 變數名)
- 通過迭代器可以讀取它指向的元素
- 非const迭代器和可以修改其指向的元素
順序容器
何為順序容器,也就是說元素的位置是順序插入的,插入位置與元素的值無關,核心是容器沒有排序。 順序容器都具有以下10個成員函式:
#返回迭代器
begin
end
rbegin //返回指向最後一個元素的迭代器
rend
#返回元素引用
front
back
#其他
erase(刪除區間時返回被刪除元素後面的迭代器;也可刪除一個或幾個元素)
clear
push_back
pop_back
複製程式碼
順序容器有以下三種:
1.vector動態陣列 標頭檔案<vetor>
元素在記憶體中是連續存放的。
隨機存取時間:常數時間(因為可以通過下標直接訪問到地址)。
在尾部增刪元素通常是常數時間(正常是常數時間,如果超出了預設分配的元素個數,會重新分配儲存空間,此時會消耗更多時間)。
在中間或者頭部增刪元素:o(n)(會移動其他元素的位置)。
迭代器型別:隨機訪問(支援下標訪問、隨機移動,例:a[i])。
查詢時間:o(n)(因為沒有排序,只能現行查詢,效率較低)。
可見vector在中間或者頭部增刪元素效能較低。
優點:記憶體和C完全相容、高效隨機訪問、節省空間
缺點:內部插入刪除元素代價巨大、動態大小查過自身容量需要申請大量記憶體做大量拷貝。
建構函式:
vector();
vector(int n);
vector(int n, const T &a); //把n個元素初始化為a
vector(iterator first,iterator last); //初始化為其他容器上區間[first,last)一致的內容
複製程式碼
常用成員函式
void pop_back();
void push_back(const T &val);
int size();
T & font();
T & back();
複製程式碼
deque[讀:dek,之前經常讀dkju:]雙向佇列 標頭檔案<deque>
元素在記憶體中連續存放(連續記憶體的容器有個明顯的缺點,就是有新元素插入或老元素刪除的時候,為了給新元素騰出位置或者填充老元素的空缺,同一塊記憶體中的其他資料需要進行整體的移位,這種移位的拷貝代價有時是非常巨大的。deque實際上是分配到不同記憶體塊,通過連結串列把記憶體塊連在一起,再進行連續存放,是list與vector的折中)。
隨機存取時間:常數時間(僅次於vector,因為有可能存在尾部的記憶體位置在頭部之前的場景)。
在兩端增刪元素通常是常數時間。(deque不像vector沒有容量,不需要重新分配記憶體空間。這是因為deque由動態分配的連續空間,即緩衝區,組合而成,隨時可以增加一段新的空間連結起來。它沒有必要像vector那樣“因舊空間不足而重新分配2倍的空間,然後複製元素,再釋放舊空間”。當重新分配緩衝區時,耗時增加)。
在中間插入:時間複雜度較高。
迭代器型別:隨機訪問(效率低於vector)。
查詢時間:o(n)(原因同上)。
**優點:高效隨機訪問、內部插入刪除元素效率方便、兩端push、pop效率很高 **
**缺點:記憶體佔用比較高 **
list雙向連結串列 標頭檔案<list>
元素在記憶體中不連續分配(因為指標可以獲取前後元素的地址),所以不支援隨機存取。
在任何位置增刪元素時間:常數時間。
查詢時間:o(n)(原因同上)。
迭代器型別:雙向(不支援下標訪問,不支援迭代器的比較運算子,和+-運算子)
優點:任意位置插入刪除元素常量時間複雜度、兩個容器融合是常量時間複雜度
缺點:不支援隨機訪問、比vector佔用更多的儲存空間
常用成員函式:(注意這些在順序容器中都是list獨有的)
push_front
pop_front
sort //不支援STLsort演算法
remove
unique
merge
reverse
splice
複製程式碼
特別說明,list的sort函式有無參和compare兩個版本
list<T> classname
classname.sort(compare); //compare自定義
classname.sort();
複製程式碼
關聯容器(迭代器型別:雙向)
關聯的意思就是元素是排序的。
插入與檢索元素時間: o(log(N))(因為通過紅黑二叉樹實現,時間與平衡二叉樹一致)。
需要注意的是,STL中有些演算法比如sort,binary_search需要通過隨機訪問迭代器訪問容器中的元素,因此list以及關聯容器就不能支援該演算法!
主要包括以下4種:
- set
- multiset
- map
- multimap
除了個容器都有的成員函式外,它們都有以下成員函式
find
lower_bound
upper_bound
equal_range
count
insert
複製程式碼
map與set的不同點在於,map中存放的元素有且僅有兩個成員變數,first(類似於key),second(類似於value),map可以根據first對元素排序和檢索。
set與multiset的不同在於,後者允許存在相同值的元素。 map與multimap的不同在於,後者允許存在相同的first值的元素。
容器介面卡(不支援迭代器)
這個概念聽起來有點抽象,其實就是STL幫我們抽象了實際工作中所需要的資料操作方式。舉個例子,交流電可以有任意的傳輸電壓,但是實際生活中我們只需要220v就夠了,變壓器幫我們實現了這個基本的功能。同理,容器的操作方式多種多樣,但是其實我們只需要他們按照實際的一些規範來操作即可。
他們都有以下3個成員函式:
push
top
pop
複製程式碼
STL中的排序,查詢,變序演算法都不適合容器介面卡(因為容器介面卡的元素位置不可隨意改變,並且訪問有一定的訪問規則,不支援隨機訪問)。
stack 棧 標頭檔案<stack>
是一個有限序列,並滿足序列中被刪除、檢索和修改的項只能是最近插入序列的項(棧頂項)。也即,先進先出LIFO。
可以用vector,deque,list(效能最差)實現,編譯器預設是用deque實現的。
比如,在程式除錯中,錯誤堆疊就是一種應用。
queue 佇列 標頭檔案 <queue>
插入只可以在尾部進行,刪除、檢索和修改只允許從頭部進行。先進先出FIFO。
queue由於pop,push發生在對頭,所以不能用vector實現,可以用list,deque實現,編譯器預設用deque實現。
比如,應用於需要保證訊息順序性的場景,如訊息佇列。
priority_queue 優先順序佇列 標頭檔案<queue>
最高優先順序元素總是第一個出列。
它需採用堆排序來保證元素總是在最前面,因此要求隨機訪問,所以不能使用list實現,可以用vector和deque實現,編譯器預設情況下用vector實現。
比如,作業系統的執行緒的排程演算法,有的是按照優先順序來排程的。
總結
-
如果需要隨機訪問,用vector
-
如果儲存元素的數目已知,用vector
-
需要任意位置隨機插入刪除,用list
-
經常在容器的首部尾部插入刪除元素,用deque
-
元素結構複雜用list,也可以用vector儲存指標(需要額外的精力去維護記憶體),看需求
-
如果操作是基於鍵值,用set/map
-
如果需要經常的搜尋,用map/set
參考: