1. 程式人生 > >STL常用容器詳細解析

STL常用容器詳細解析

STL容器的實現原理 

 
STL共有六大元件
 1、容器。2、演算法。3、迭代器。4、仿函式。6、介面卡。

STL容器的實現原理

STL來管理資料十分方便,省去了我們自己構建資料結構的時間.其實,STL的實現也是基於我們常見的資料結構.


序列式容器:
vector-陣列,元素不夠時再重新分配記憶體,拷貝原來陣列的元素到新分配的陣列中。
list-單鏈表。
deque-分配中央控制器map(並非map容器),map記錄著一系列的固定長度的陣列的地址.記住這個map僅僅儲存的是陣列的地址,真正的資料在陣列中存放著.deque先從map中央的位置(因為雙向佇列,前後都可以插入元素)找到一個數組地址,向該陣列中放入資料,陣列不夠時繼續在map中找空閒的陣列來存資料。當map也不夠時重新分配記憶體當作新的map,把原來map中的內容copy的新map中。所以使用deque的複雜度要大於vector,儘量使用vector。

stack-基於deque。

queue-基於deque。
heap-完全二叉樹,使用最大堆排序,以陣列(vector)的形式存放。
priority_queue-基於heap。
slist-雙向連結串列。

關聯式容器:
set,map,multiset,multimap-基於紅黑樹(RB-tree),一種加上了額外平衡條件的二叉搜尋樹。


hash table-散列表。將待存資料的key經過對映函式變成一個數組(一般是vector)的索引,例如:資料的key%陣列的大小=陣列的索引(一般文字通過演算法也可以轉換為數字),然後將資料當作此索引的陣列元素。有些資料的key經過演算法的轉換可能是同一個陣列的索引值(碰撞問題,可以用線性探測,二次探測來解決),STL是用開鏈的方法來解決的,每一個數組的元素維護一個list,他把相同索引值的資料存入一個list,這樣當list比較短時執行刪除,插入,搜尋等演算法比較快。


hash_map,hash_set,hash_multiset,hash_multimap-基於hash table。

綜上所述大家應該知道了什麼情況下該使用哪一個STL容器更合適,可以在適當時候避免使用一些影響效率的STL容器.


這裡我們不涉及容器的基本操作之類,只是要討論一下各個容器其各自的特點STL中的常用容器包括:順序性容器(vector、deque、list)、關聯容器(map、set)、容器介面卡(queue、stac)

STL是C/C++開發中一個非常重要的模板,而其中定義的各種容器也是非常方便我們大家使用。下面,我們就淺談某些常用的容器。這裡我們不涉及容器的基本操作之類,只是要討論一下各個容器其各自的特點。STL中的常用容器包括:順序性容器(vector、deque、list)、關聯容器(map、set)、容器介面卡(queue、stac)。

1、順序性容器

(1)vector
vector是一種動態陣列,在記憶體中具有連續的儲存空間,支援快速隨機訪問。由於具有連續的儲存空間,所以在插入和刪除操作方面,效率比較慢。vector有多個建構函式,預設的建構函式是構造一個初始長度為0的記憶體空間,且分配的記憶體空間是以2的倍數動態增長的,即記憶體空間增長是按照20,21,22,23.....增長的,在push_back的過程中,若發現分配的記憶體空間不足,則重新分配一段連續的記憶體空間,其大小是現在連續空間的2倍,再將原先空間中的元素複製到新的空間中,效能消耗比較大,尤其是當元素是非內部資料時(非內部資料往往構造及拷貝建構函式相當複雜)。vector的另一個常見的問題就是clear操作。clear函式只是把vector的size清為零,但vector中的元素在記憶體中並沒有消除,所以在使用vector的過程中會發現記憶體消耗會越來越多,導致記憶體洩露,現在經常用的方法是swap函式來進行解決:  

vector<int> V;V.push_back(1); V.push_back(2);V.push_back(1); V.push_back(2);
vector<int>().swap(V); 或者 V.swap(vector<int>());

利用swap函式,和臨時物件交換,使V物件的記憶體為臨時物件的記憶體,而臨時物件的記憶體為V物件的記憶體。交換以後,臨時物件消失,釋放記憶體。

-------------------

STL: 知道vector嗎,一個自然數陣列,要刪除其中的奇數,寫出程式碼。這個需要注意的是erase刪除結點,則該結點的iterator會失效,同時erase會返回下一個有效迭代器,所以iter++只有在偶數的時候才執行。 繼續問到iter++和++iter的區別,回答前者會產生一個臨時變數,後者的效率更高,如果前面有=的話,得到的值不一樣。 vector是怎麼儲存的,如果讓你實現vector,你怎麼做,首先說了下STL裡面的vector,記憶體如何分配的,建構函式等等。開始用陣列實現vector,然後問一定要寫成類似MS提供的STL裡面的vector,提示說不用,寫個基本框架就可以了,就直接寫vector程式碼,然後寫vector的插入操作,注意vector滿時

-------------------


(2)deque
deque和vector類似,支援快速隨機訪問。二者最大的區別在於,vector只能在末端插入資料,而deque支援雙端插入資料。deque的記憶體空間分佈是小片的連續,小片間用連結串列相連,實際上內部有一個map的指標。deque空間的重新分配要比vector快,重新分配空間後,原有的元素是不需要拷貝的。

(3)list
list是一個雙向連結串列,因此它的記憶體空間是可以不連續的,通過指標來進行資料的訪問,這使list的隨機儲存變得非常低效,因此list沒有提供[]操作符的過載。但list可以很好地支援任意地方的插入和刪除,只需移動相應的指標即可。

(4)在實際使用時,如何選擇這三個容器中哪一個,應根據你的需要而定,一般應遵循下面的原則:
    1) 如果你需要高效的隨即存取,而不在乎插入和刪除的效率,使用vector
    2) 如果你需要大量的插入和刪除,而不關心隨即存取,則應使用list
    3) 如果你需要隨即存取,而且關心兩端資料的插入和刪除,則應使用deque

2、關聯容器

(1)map
map是一種關聯容器,該容器用唯一的關鍵字來對映相應的值,即具有key-value功能。map內部自建一棵紅黑樹(一種自平衡二叉樹),這棵樹具有資料自動排序的功能,所以在map內部所有的資料都是有序的,以二叉樹的形式進行組織。這是map的模板:

template < class Key, class T, class Compare= less<Key>, class Allocator=allocator< pair<const Key,T> > > class map;

從模板中我們可以看出,再構造map時,是按照一定的順序進行的。map的插入和刪除效率比其他序列的容器高,因為對關聯容器來說,不需要做記憶體的拷貝和移動,只是指標的移動。由於map的每個資料對應紅黑樹上的一個節點,這個節點在不儲存你的資料時,是佔用16個位元組的,一個父節點指標,左右孩子指標,還有一個列舉值(標示紅黑色),所以map的其中的一個缺點就是比較佔用記憶體空間。

map簡介

 map是STL的一個關聯容器associativecontainer)之一,它提供一對一(其中第一個可以稱為關鍵字,每個關鍵字只能在map中出現一次,第二個可能稱為該關鍵字的值)的資料處理能力,由於這個特性,它完成有可能在我們處理一對一資料的時候,在程式設計上提供快速通道。這裡說下map內部資料的組織,map內部自建一顆紅黑樹(一種非嚴格意義上的平衡二叉樹),這顆樹具有對資料自動排序的功能,所以在map內部所有的資料都是有序的。

一個map是一個鍵值對序列,          即(key ,value)對。它提供基於key的快速檢索能力,在一個map中key值是唯一的。map提供雙向迭代器,即有從前往後的(iterator),也有從後往前的(reverse_iterator)。

map中key值是唯一 

    例子:如果已存在一個鍵值對(編號,使用者名稱):(1001,"jack"),而我們還想插入一個鍵值對(1001,"mike") 執行時會報錯(不是報錯,準確的說是,返回插入不成功!)。而我們又的確想這樣做,即一個鍵對應多個值,幸運的是multimap可是實現這個功能。

map中的型別定義

關聯陣列(associative array)是最有用的使用者定義型別之一,經常內建在語言中用於文字處理等。一個關聯陣列通常也稱為map,有時也稱字典(dictionary),儲存一對值。第一個值稱為key、第二個稱為對映值mapped-value。

// map::begin/end
#include <iostream>
#include <map>

int main ()
{
  std::map<char,int> mymap;
  std::map<char,int>::iterator it;

  mymap['z'] = 100;
  mymap['a'] = 200;
  mymap['z'] = 300;

  // show content:
  for (std::map<char,int>::iterator it=mymap.begin(); it!=mymap.end(); ++it)
    std::cout << it->first << " => " << it->second << '\n';

  return 0;
}

a => 200
z => 300



(2)set
set也是一種關聯性容器,它同map一樣,底層使用紅黑樹實現,插入刪除操作時僅僅移動指標即可,不涉及記憶體的移動和拷貝,所以效率比較高。set中的元素都是唯一的,而且預設情況下會對元素進行升序排列。所以在set中,不能直接改變元素值,因為那樣會打亂原本正確的順序,要改變元素值必須先刪除舊元素,再插入新元素。不提供直接存取元素的任何操作函式,只能通過迭代器進行間接存取。set模板原型:

template <class Key, class Compare=class<Key>, class Alloc=STL_DEFAULT_ALLOCATOR(Key) > class set;
set支援集合的交(set_intersection)、差(set_difference)、並(set_union)及對稱差(set_symmetric_difference) 等一些集合上的操作。

3、容器介面卡

(1)queue
queue是一個佇列,實現先進先出功能,queue不是標準的STL容器,卻以標準的STL容器為基礎。queue是在deque的基礎上封裝的。之所以選擇deque而不選擇vector是因為deque在刪除元素的時候釋放空間,同時在重新申請空間的時候無需拷貝所有元素。其模板為:
template < TYPENAME _Sequence="deque<_TP" typeneam _Tp,> > class queue;

(2)stack
stack是實現先進後出的功能,和queue一樣,也是內部封裝了deque,這也是為啥稱為容器介面卡的原因吧(純屬猜測)。自己不直接維護被控序列的模板類,而是它儲存的容器物件來為它實現所有的功能。stack的原始碼原理和實現方式均跟queue相同。

下面是他們的區別:

我們常用到的STL容器有vector、list、deque、map、multimap、set和multiset,它們究竟有何區別,各自的優缺點是什麼,為了更好的揚長避短,提高程式效能,在使用之前需要我們瞭解清楚。

verctor

vector類似於C語言中的陣列,它維護一段連續的記憶體空間,具有固定的起始地址,因而能非常方便地進行隨機存取,即 [] 操作符,但因為它的記憶體區域是連續的,所以在它中間插入或刪除某個元素,需要複製並移動現有的元素。此外,當被插入的記憶體空間不夠時,需要重新申請一塊足夠大的記憶體並進行記憶體拷貝。值得注意的是,vector每次擴容為原來的兩倍,對小物件來說執行效率高,但如果遇到大物件,執行效率就低了。

list

list類似於C語言中的雙向連結串列,它通過指標來進行資料的訪問,因此維護的記憶體空間可以不連續,這也非常有利於資料的隨機存取,因而它沒有提供 [] 操作符過載。

deque

deque類似於C語言中的雙向佇列,即兩端都可以插入或者刪除的佇列。queue支援 [] 操作符,也就是支援隨機存取,而且跟vector的效率相差無幾。它支援兩端的操作:push_back,push_front,pop_back,pop_front等,並且在兩端操作上與list的效率
也差不多。或者我們可以這麼認為,deque是vector跟list的折中。

map

map類似於資料庫中的1:1關係,它是一種關聯容器,提供一對一(C++ primer中文版中將第一個譯為鍵,每個鍵只能在map中出現一次,第二個被譯為該鍵對應的值)的資料處理能力,這種特性了使得map類似於資料結構裡的紅黑二叉樹。

multimap

multimap類似於資料庫中的1:N關係,它是一種關聯容器,提供一對多的資料處理能力。

set

set類似於數學裡面的集合,不過set的集合中不包含重複的元素,這是和vector的第一個區別,第二個區別是set內部用平衡二叉樹實現,便於元素查詢,而vector是使用連續記憶體儲存,便於隨機存取。

multiset

multiset類似於數學裡面的集合,集合中可以包含重複的元素。

小結

在實際使用過程中,到底選擇這幾種容器中的哪一個,應該根據遵循以下原則:

1、如果需要高效的隨機存取,不在乎插入和刪除的效率,使用vector;

2、如果需要大量的插入和刪除元素,不關心隨機存取的效率,使用list;

3、如果需要隨機存取,並且關心兩端資料的插入和刪除效率,使用deque;

4、如果打算儲存資料字典,並且要求方便地根據key找到value,一對一的情況使用map,一對多的情況使用multimap;

5、如果打算查詢一個元素是否存在於某集合中,唯一存在的情況使用set,不唯一存在的情況使用multiset。

常用STL容器舉例

一 常用容器舉例 

1 vector:

       vector類似於動態陣列,直接訪問元素,從後面快速插入或者刪除,示例程式碼如下:

  1. #include <iostream>
  2. #include <vector>//包含vector
  3. usingnamespace std;//指定名稱空間
  4. int main()  
  5. {  
  6.     cout<<"----------vector test-----------"<<endl;  
  7.     //定義一個vector
  8.     vector <int> vect;  
  9.     vector <int> vect1(12);//12個int型別元素,每個元素的初始值均為0
  10.     vector <int> vect2(12,9);//12個int,初試值均為9
  11.     //使用陣列初始化vector
  12.     int a[]={0,1,2,3,4,5,6,7,8,9,0};  
  13.     //vector <資料型別> <容器名> (<開始地址>,<結束地址的下一個地址> )。執行過vt中元素為1,2,3
  14.     vector <int> vt(a+1,a+4);  
  15.     //在尾部壓入3個值
  16.     vt.push_back(1);  
  17.     vt.push_back(2);  
  18.     vt.push_back(3);  
  19.     //定義迭代器iterator
  20.     vector <int>::iterator iter=vt.begin();//起始地址
  21.     vector <int>::iterator iter_end=vt.end();//結束地址,兩個地址都是指標型別
  22.     //遍歷vt
  23.     for(;iter!=iter_end;iter++)  
  24.     {  
  25.         cout<<*iter<<endl;  
  26.     }  
  27.     //彈出一個元素
  28.     vt.pop_back();  
  29.     //以下兩行重新獲得起始和結尾地址
  30.     iter=vt.begin();  
  31.     iter_end=vt.end();  
  32.     cout<<"----------executed pop_back------"<<endl;  
  33.     for(;iter!=iter_end;iter++)  
  34.     {  
  35.         cout<<*iter<<endl;  
  36.     }  
  37.     //插入元素
  38.     cout<<"----------insert into------------"<<endl;  
  39.     //插入格式:vector.insert(<起始地址>,<插入的數量>,<元素值>);如果插入的數量為1,則第二個引數可以被省略
  40.     vt.insert(vt.begin()+1,3,9);  
  41.     iter=vt.begin();  
  42.     iter_end=vt.end();  
  43.     for(;iter!=iter_end;iter++)  
  44.     {  
  45.         cout<<*iter<<endl;  
  46.     }  
  47.     //刪除元素
  48.     cout<<"----------erase-------------------"<<endl;  
  49.     //刪除格式1為:vector.erase(<刪除元素的地址>);
  50.     //刪除格式2為:vector.erase(<刪除元素的起始地址>,<終止地址>);
  51.     iter=vt.begin();  
  52.     iter_end=vt.end();  
  53.     vt.erase(iter+1,iter_end);//刪除第二個到最後一個的元素
  54.     iter_end=vt.end();  
  55.     for(;iter!=iter_end;iter++)  
  56.     {  
  57.         cout<<*iter<<endl;  
  58.     }  
  59.     return 1;  
  60. }  

   2  list

  list 為雙向連結串列,可以從任何地方插入或者刪除的,其示例程式碼如下:

  1. #include <iostream>
  2. #include <list>
  3. usingnamespace std;  
  4. void main()  
  5. {  
  6.     list<int> c1;  
  7.     c1.push_back(1);//從尾部push資料(結點)到list中 
  8.     c1.push_back(2);   
  9.     c1.push_back(3);  
  10.     c1.push_back(4);  
  11.     c1.push_front(0);//從頭部push資料(結點)到list中 
  12.     c1.pop_back();//從尾部pop資料(結點)出去 
  13.     int& i = c1.back();//獲取list中尾部資料(結點) 
  14.     constint& ii = c1.front();//獲取list中頭部 資料(結點)
  15. 相關推薦

    STL常用容器詳細解析

    STL容器的實現原理    STL共有六大元件  1、容器。2、演算法。3、迭代器。4、仿函式。6、介面卡。 STL容器的實現原理 STL來管理資料十分方便,省去了我們自己構建資料結構的時間.其實,STL的實現也是基於我們常見的資料結

    STL常用容器,以及一些函式

    lower_bound()      lower_bound(a, a+n, x);//a是陣列,n是長度      查詢“大於或者等於x的第一個位置” isalpha(ch): &

    java容器詳細解析

    前言:在java開發中我們肯定會大量的使用集合,在這裡我將總結常見的集合類,每個集合類的優點和缺點,以便我們能更好的使用集合。下面我用一幅圖來表示 其中淡綠色的表示介面,紅色的表示我們經常使用的類。 1:基本概念 Java容器類類庫的用途是儲存物件,可以將其分為2個概念。 1.1:Collecti

    【C++】STL常用容器總結之十二:string類

    13、string類 宣告 string類本不是STL的容器,但是它與STL容器有著很多相似的操作,因此,把string放在這裡一起進行介紹。 之所以拋棄char*的字串而選用C++標準程式庫中的string類,是因為他和前者比較起來,不必擔心記憶體是

    【C++】STL常用容器總結之四:連結串列list

    5、連結串列list List是每個節點包含前驅指標、後繼指標和資料域三個部分的雙向連結串列。List不提供隨機存取,訪問元素需要按順序走到需存取的元素,時間複雜度為O(n),在list的任何位置上執行插入或刪除操作都非常迅速,只需在list內部調整一下指標。

    【C++】STL常用容器總結之八:對映map

    9、對映map Map是鍵-值對的集合,map中的所有元素都是pair,可以使用鍵作為下標來獲取一個值。Map中所有元素都會根據元素的值自動被排序,同時擁有實值value和鍵值key,pair的第一元素被視為鍵值,第二元素被視為實值,同時map不允許兩個元素有

    STL常用容器對比

        STL的常用容器大致有以下8個: 1.vector     vector是一種動態陣列,在記憶體中具有連續的儲存空間,支援快速隨機訪問。由於具有連續的儲存空間,所以在插入和刪除操作方面,效率比較慢。vector有多個建構函式,預設的建構函式是構造一個初始長度為0的記

    【C++】STL常用容器總結之五:雙端佇列deque

    6、雙端佇列deque 所謂的deque是”double ended queue”的縮寫,雙端佇列不論在尾部或頭部插入元素,都十分迅速。而在中間插入元素則會比較費時,因為必須移動中間其他的元素。雙端佇列是一種隨機訪問的資料型別,提供了在序列兩端快速插入和刪除操

    【C++】STL常用容器總結之九:集合set

    10、集合set Map容器是鍵值對的集合,而set容器只是單純的鍵的集合,當只想知道一個值是否存在時,使用set容器是最合適的。在set中,所有元素都會根據其鍵值被自動排序,同時set中不允許兩個元素有相同的鍵值。 1、set容器的一些操作 Set容

    C++中STL常用容器的優點和缺點

    我們常用到的STL容器有vector、list、deque、map、multimap、set和multiset,它們究竟有何區別,各自的優缺點是什麼,為了更好的揚長避短,提高程式效能,在使用之前需要我們瞭解清楚。 verctor vector類似於C語言中的陣列,它維護一段連續的記憶體空間,具有固定的

    【C++】STL常用容器總結之一:容器與迭代器

    宣告: 1、本博文主要整理自《C++ Primer》和《STL原始碼剖析》這兩本經典書籍。同時,也參考了網路中不少優秀部落格,對這些部落格的作者表示感謝。 2、由於博主能力有限,對於一些容器的用法可能尚未進行深入研究。因此,本博文若有錯誤和不足之處,歡迎大家

    【C++】STL常用容器總結之十一:容器小結

    12、容器小結 1、容器的選用 Vector和deque容器提供了對元素的快速訪問,但付出的代價是,在容器的任意位置插入或刪除元素,比在容器尾部插入和刪除的開銷更大,因為要保證其連續儲存,需要移動元素;list型別在任何位置都能快速插入和刪除,因為不需要

    STL常用容器總結——stack棧

    原文 棧中的資料是先進後出(FILO),棧只有一個出口,新增和移除元素都只能在棧頂操作。在STL中,棧是以別的容器作為底部結構,修改介面使其符合棧的特性。預設情況下,棧使用deque作為其底層資料結構,也可以指定使用vector或list等。 棧常用的函式有: 1.資料操作

    STL常用容器大致對比

    1.vector    vector是一種動態陣列,在記憶體中具有連續的儲存空間,支援快速隨機訪問。由於具有連續的儲存空間,所以在插入和刪除操作方面,效率比較慢。vector有多個建構函式,預設的建構函式是構造一個初始長度為0的記憶體空間,且分配的記憶體空間是以2的倍數動態增

    java 常用關鍵詞詳細解析,讓你入門java

    java語法中有一些詞被賦予了特殊含義,他們有固定用法,我們必須遵從,下面我就簡單的說一說用法及格式: 關鍵字包括abstract  boolean  break  byte  case  catch  char  class  continue  default  do

    C/C++STL常用容器用法總結

    一、容器概念:容器是儲存其他物件的物件。被儲存的物件必須是同一型別。基本特徵:以下用X表示容器型別(後面會講到),T表示儲存的物件型別(如int);a和b表示為型別X的值;u表示為一個X容器的識別符號(

    c++ STL 常用容器元素型別相關限制 指標 引用

    http://blog.csdn.net/ginewar/article/details/20247215   c+

    C++STL幾種常用容器簡要歸納

    本文參考李煜東《演算法競賽進階指南》,筆者作歸納總結。 本文將簡要介紹STL中vector,queue,priority_queue,deque,set,multiset,map,bitset八種容器及其操作方法。 vector 使用此容器需在程式前加上標頭檔

    Java開發Swing實戰JFrame和JTabbedPane容器的用法詳細解析

    概述: 專案是一個桌面程式,涉及標籤和按鈕元件、佈局管理器元件、面板元件、列表框和下拉框元件等元件,以及Swing事件處理機制。 下面先從最基礎的介面開始。 /** @author: lishuai @date: 2018/11/26 13:51 */ public cl

    STL常用容器的選擇

    今天去面試問到了stl的常用容器演算法問題,但是提前沒準備,平時也沒太在意,還有就是忘了。總之,回答得很狼狽。 希望能在這裡整理一下,首先看了一下《STL原始碼剖析》中對STL六大元件是這樣介紹的: 容器(containers):各種資料結構,用來存放資料。從實現的角度看,