一.各種容器的特性
vector
典型的序列容器,c++標準嚴格要求次容器的實現記憶體必須是連續的,唯一可以和標準c相容的stl容器,任意元素的讀取、修改具有常數時間複雜度,在序列尾部進行插入、刪除是常數時間複雜度,但在序列的頭部插入、刪除的時間複雜度是o(n),可以 在任何位置插入新元素,有隨機訪問功能,插入刪除操作需要考慮。
deque
序列容器,記憶體也是連續的,和vector相似,區別在於在序列的頭部插入和刪除操作也是常數時間複雜度, 可以 在任何位置插入新元素,有隨機訪問功能。
list
序列容器,記憶體是不連續的,任意元素的訪問、修改時間複雜度是o(n),插入、刪除操作是常數時間複雜度, 可以 在任何位置插入新元素。
set
關聯容器,元素不允許有重複,資料被組織成一棵紅黑樹,查詢的速度非常快,時間複雜度是o(logn)
multiset
關聯容器,和set一樣,卻別是允許有重複的元素,具備時間複雜度o(logn)查詢功能。
map
關聯容器,按照方式組成集合,按照鍵組織成一棵紅黑樹,查詢的時間複雜度o(logn),其中鍵不允許重複。
multimap
和map一樣,區別是鍵可以重複
二.容器的一種分類
連續記憶體的容器:這種型別容器包含vector、deque。特點是在一塊連續的記憶體塊上存放資料,所以有資料插入和刪除的時候,如果不是在序列的 或者兩端那麼花費的代價是非常大的,因為需要保證連續記憶體,同時給新元素騰出空間或者填充刪除元素的空間,如果儲存的是複雜結構的話就要花費大量的時間進行拷貝操作(可以儲存複雜結構的指標來彌補這個缺陷,這個討論在另個總結中進行)。
基於節點的容器:這類容器是剩餘的幾個list、set、multiset、map、multimap.這類容器中的資料是分別儲存在不同的記憶體塊中,可能連續也可能不連續(一般不認為是連續的),這樣的容器在插入刪除元素的時候修改的只是節點的指標,這樣的消耗是非常小的。
三. 使用中需要考慮的一些因素
在使用的過程中,需要考慮的問題有元素順序、標準的一致性、迭代器能力、記憶體布局和c的相容性、查詢速度這些,考慮了這些問題你選擇的容器應該會非常適合你當前的情景。
1.需要大量新增新元素:
vector在大量新增元素的時候問題最大,因為他的一種最常見的記憶體分配實現方法是當前的容量(capacity)不足就申請一塊當前容量2倍的新記憶體空間,然後將所有的老元素全部拷貝到新記憶體中,新增大量元素的時候的花費的驚人的大。如果由於其他因素必須使用vector,並且還需要大量新增新元素,那麼可以使用成員函式reserve來事先分配記憶體,這樣可以減少很多不必要的消耗。
list對這種情況的適應能力就非常好,都是常數時間的插入消耗。deque前面說過了,他是vector和list的折衷形式,記憶體不夠了就申請一塊新的記憶體,但並不拷貝老的元素。
2.查詢速度:
這個因素主要取決於演算法,而演算法最終是作用在容器中元素上的,所以這裡的查詢速度指的是容器能夠達到的最好查詢效率。
對於序列容器需要分兩種情況,區分依據是元素是否排序,1)對於已經排序的序列容器,使用binary_search、lower_bound、upper_bound、equal_range可以獲得對數時間複雜度的查詢速度(o(logn));2)而未排序的序列容器二分查詢肯定是用不了,能達到的最好的時間複雜度是線性的(o(n))。
對於關聯容器,儲存的時候儲存的是一棵紅黑樹(一種更為嚴格的平衡二叉樹,文件最後有介紹),總是能達到對數時間複雜度(o(logn))的效率,因為關聯容器是按照鍵值排好序的。
3.是否是連續記憶體:
連續記憶體的容器有個明顯的缺點,就是有新元素插入或老元素刪除的時候,為了給新元素騰出位置或者填充老元素的空缺,同一塊記憶體中的其他資料需要進行整體的移位,這種移位的拷貝代價有時是非常巨大的。標準容器中的vector、deque是連續記憶體的,其中vector是完全連續記憶體,而deque是vector和list的折衷實現,是多個記憶體塊組成的,每個塊中存放的元素連續記憶體,而記憶體塊又像鍊錶一樣連線起來。
所以需要考慮在操作的過程中是否有在任意位置插入元素的需求,有這種需求的話盡量避免使用連續記憶體的vector、deque
4.元素的排序:
序列容器中的元素不會自動排序,程式設計師插入什麼順序記憶體中就是什麼順序,而關聯容器不是這樣的,他會以自己的鍵值按照某種等價關係(equivalence)進行排序。所以預設情況下序列容器中的元素是無序的,而關聯容器中的元素是有序的。
所以容器在遍歷元素的時候序列容器輸出的順序和插入的順序式一致的,關聯容器就不一定了。下面給出兩個例子:
輸出結果如下:
通過例子看到序列容器vector遍歷的順序和插入的順序是一樣的,而關聯容器set把插入的元素按照某種順序重新組織了,所以選擇容器的時候如果很在意插入順序的話就選擇序列容器。
5.記憶體是否和c相容:
適合的容器只有乙個vector,意思就是如果需要把容器中的資料放到c型別的陣列中那麼不需要做多餘複雜的操作,如果有vectorv,只需要直接使用&v[0]就可以得到v中第乙個元素的指標,因為vector和c陣列的記憶體布局是一樣的,這個要求同時也是標準c++委員會制定的標準。所以能保證有這樣特性的容器只有vector,那麼vector以外的其他stl容器中的資料如果需要變換成c陣列形式,或者c陣列放到其他型別容器中,可以把vector作為乙個橋梁,下面給個例子:
//假設函式
void read(const int* pint, unsigned int num);
//從pint
指標位置開始讀取
num個
int型資料
std::set mset;
... //
省略給mset
插入元素的操作
std::vector mvector(mset.begin(), mset.end());
if (!mvector.empty())
read(&mvector[0], mvector.size());
四.各種容器的優缺點:
用哪種容器的選擇看起來非常繁瑣,頭腦中如果有個每個容器大概的模型,在選擇的時候會更為輕鬆點。
1. vector的資料模型就是陣列。
優點:記憶體和c完全相容、高效隨機訪問、節省空間
缺點:內部插入刪除元素代價巨大、動態大小查過自身容量需要申請大量記憶體做大量拷貝。
2. list 的資料結構模型是鍊錶
優點:任意位置插入刪除元素常量時間複雜度、兩個容器融合是常量時間複雜度
3. deque的資料模型是陣列和鍊錶的折衷:
優點:高效隨機訪問、內部插入刪除元素效率方便、兩端push pop
缺點:記憶體占用比較高
4. map、set、multimap、multiset的資料結構模型是二叉樹(紅黑樹)
優點:元素會按照鍵值排序、查詢是對數時間複雜度、通過鍵值查元素、map提供了下標訪問
五.綜合一下該用什麼?
首先說說vector、list、deque
1) 如果需要隨機訪問,用vector
2) 如果儲存元素的數目已知,用vector
3) 需要任意位置隨機插入刪除,用list
4) 只有需要更多在容器的首部尾部插入刪除元素,用deque
5) 元素是複雜結構用list,也可以用vector儲存指標(需要額外的精力去維護記憶體),看需求
6) 如果操作是基於鍵值,用set map
7) 如果需要經常的搜尋,用map set
8) map set 的區別是map中的元素都是pair,同時map提供下標訪問 ,也是個陷阱,這個once在日誌裡講過
如果實在不想細看上面說的繁瑣的內容,就照下面這個流程走一遍,就會找到最終的歸宿
這個是照著乙個老外的個人主頁上總結的圖畫的
附錄:標準關聯容器實現的紅黑樹,這個大哥(姐)講的挺好的
C STL容器適用情況的說明
各種容器的特性 vector 典型的序列容器,c 標準嚴格要求次容器的實現記憶體必須是連續的,唯一可以和標準 c相容的 stl容器,任意元素的讀取 修改具有常數時間複雜度,在序列尾部進行插入 刪除是常數時間複雜度,但在序列的頭部插入 刪除的時間複雜度是 o n 可以在任何位置插入新元素,有隨機訪問功...
Thread各方法適用情況
try catch interruptedexception e systemclock.sleep 2000 sleep 方法會使執行緒睡眠,交出cpu給其他執行緒使用。注意 sleep 方法不會釋放鎖。上述第二個方法為android獨有,兩者的區別就為是否catch interruptedexc...
卡特蘭數的證明和常見的適用情況
請支援原創 卡特蘭數是一種經典的組合數,經常出現在各種計算中,其前幾項為 132,429,1430,4862,16796,58786,208012,742900,2674440,9694845,35357670,129644790,477638700,1767263190,6564120420,24...