1. 程式人生 > >C++ vector、list和deque的區別 (整理)

C++ vector、list和deque的區別 (整理)

style stl 內存大小 splice cnblogs 類定義 所有 編寫 重復

1.vector數據結構
  vector和數組類似,擁有一段連續的內存空間,並且起始地址不變。因此能高效的進行隨機存取,時間復雜度為o(1);但因為內存空間是連續的,所以在進行插入和刪除操作時,會造成內存塊的拷貝,時間復雜度為o(n)。另外,當數組中內存空間不夠時,會重新申請一塊內存空間並進行內存拷貝。

vector實現原理:

  STL內部實現時,首先分配一個非常大的內存空間預備進行存儲,即capacity()函數返回的大小,當超過此分配的空間時再整體重新放分配一塊內存存儲(VS6.0是兩倍,VS2005是1.5倍),所以這給人以vector可以不指定vector即一個連續內存的大小的感覺。通常此默認的內存分配能完成大部分情況下的存儲。

擴充空間(不論多大)都應該這樣做:
(1)配置一塊新空間
(2)將舊元素一一搬往新址
(3)把原來的空間釋放還給系統

  註:vector 的數據安排以及操作方式,與array 非常相似。兩者的唯一差別在於空間的利用的靈活性。Array 的擴充空間要程序員自己來寫。

 vector類定義了好幾種構造函數,用來定義和初始化vector對象:
vector<T> v1; vector保存類型為T的對象。默認構造函數v1為空。
vector<T> v2(v1); v2是v1的一個副本。
vector<T> v3(n, i); v3包含n個值為i的元素。
vector<T> v4(n); v4含有值初始化的元素的n個副本。

2.list數據結構

  非連續存儲結構,具有雙向鏈表結構,每個元素維護一對前向和後向指針,因此支持前向/後向遍歷。支持高效的隨機插入/刪除操作,但隨機訪問效率低下,且由於需要額外維護指針,開銷也比較大。每一個結點都包括一個信息快Info、一個前驅指針Pre、一個後驅指針Post。可以不分配必須的內存大小方便的進行添加和刪除操作。使用的是非連續的內存空間進行存儲。

優點:(1) 不使用連續內存完成動態操作。
(2) 在內部方便的進行插入和刪除操作
(3) 可在兩端進行push、pop
缺點:(1) 不能進行內部的隨機訪問,即不支持[ ]操作符和vector.at()
(2) 相對於verctor占用內存多

3.deque數據結構

  連續存儲結構,即其每個元素在內存上也是連續的,類似於vector,不同之處在於,deque提供了兩級數組結構, 第一級完全類似於vector,代表實際容器;另一級維護容器的首位地址。這樣,deque除了具有vector的所有功能外,還支持高效的首/尾端插入/刪除操作。
deque 雙端隊列 double-end queue
deque是在功能上合並了vector和list。
優點:(1) 隨機訪問方便,即支持[ ]操作符和vector.at()
(2) 在內部方便的進行插入和刪除操作
(3) 可在兩端進行push、pop
缺點:占用內存多
使用區別:
(1)如果你需要高效的隨即存取,而不在乎插入和刪除的效率,使用vector
(2)如果你需要大量的插入和刪除,而不關心隨機存取,則應使用list
(3)如果你需要隨機存取,而且關心兩端數據的插入和刪除,則應使用deque

4、vector VS. list VS. deque:
a、若需要隨機訪問操作,則選擇vector;
b、若已經知道需要存儲元素的數目,則選擇vector;
c、若需要隨機插入/刪除(不僅僅在兩端),則選擇list
d、只有需要在首端進行插入/刪除操作的時候,還要兼顧隨機訪問效率,才選擇deque,否則都選擇vector。
e、若既需要隨機插入/刪除,又需要隨機訪問,則需要在vector與list間做個折中-deque。
f、當要存儲的是大型負責類對象時,list要優於vector;當然這時候也可以用vector來存儲指向對象的指針,
同樣會取得較高的效率,但是指針的維護非常容易出錯,因此不推薦使用。

問題一:list和vector的區別:
(1)vector為存儲的對象分配一塊連續的地址空間,隨機訪問效率很高。但是插入和刪除需要移動大量的數據,效率較低。尤其當vector中存儲
的對象較大,或者構造函數復雜,則在對現有的元素進行拷貝的時候會執行拷貝構造函數。
(2)list中的對象是離散的,隨機訪問需要遍歷整個鏈表,訪問效率比vector低。但是在list中插入元素,尤其在首尾插入,效率很高,只需要改變元素的指針。

(3)vector是單向的,而list是雙向的;

(4)vector中的iterator在使用後就釋放了,但是鏈表list不同,它的叠代器在使用後還可以繼續用;鏈表特有的;

使用原則:
(1)如果需要高效的隨機存取,而不在乎插入和刪除的效率,使用vector;
(2)如果需要大量高效的刪除插入,而不在乎存取時間,則使用list;
(3)如果需要高效的隨機存取,還要大量的首尾的插入刪除則建議使用deque,它是list和vector的折中;

問題二:常量容器const
const vector<int> vec(10);//這個容器裏capacity和size和值都是不能改變的,const修飾的是vector;
叠代器:const vector<int>::const_iterrator ite; //常量叠代器;
註:const vector <int> vec(10) —— 與const int a[10]是一回事,意思是vec只有10個元素,不能增加了,裏面的元素也是不能變化的

問題三:capacity V.S size
a、capacity是容器需要增長之前,能夠盛的元素總數;只有連續存儲的容器才有capacity的概念(例如vector,deque,string),list不需要capacity。
b、size是容器當前存儲的元素的數目。
c、vector默認的容量初始值,以及增長規則是依賴於編譯器的。

問題四:用vector存儲自定義類對象時,自定義類對象須滿足:
a、有可供調用的無參構造函數(默認的或自定義的);

b、有可用的拷貝賦值函數(默認的或自定義的)

1 ite=find(vec.begin(),vec.end(),88);  
2 vec.insert(ite,2,77);  //叠代器標記的位置前,插入2個77;  
3 cout<<*ite<<endl;  //會崩潰,因為叠代器在使用後就釋放了,*ite的時候就找不到它的地址了; 
 1 #include <iostream>  
 2 using namespace std;  
 3 #include <vector>   //向量的頭文件;  
 4 #include <algorithm> //算法的頭文件;  
 5 int main()  
 6 {  
 7     vector <int> vec(5,8);  
 8     //--類型是vector<int>,該容器向量中含有5個int類型的數值8,變量名為vec。  
 9     //vector是一個類模板(class template),所以必須要聲明其類型,int,一個容器中所有的對象必須是同一種類型;  
10     // 定義一個容器對象;直接構造出一個數組;用法和數組一樣;  
11     //    
12         for(int i=0;i<vec.size();i++)   //size()是指容器裏當前有多少個使用的元素;  
13         {  
14             cout<<vec[i]<<"  ";  
15         }     
16         cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;  //得到容器裏用的多少個空間,和總共的大小;  
17      vector<int>::iterator ite;  //定義了一個向量的叠代器;相當於定義了一個指針;  
18     for(ite=vec.begin();ite!=vec.end();ite++)   //得到開始、結束  
19     {  
20         cout<<*ite <<" ";  //叠代器返回的是引用:  
21     }  
22         cout<<endl;  
23     //在尾部插入;  
24     vec.push_back(9);  //VS6.0擴充的空間是兩倍;在VS2005擴充的空間是1.5倍;  
25     for(ite=vec.begin();ite!=vec.end();ite++)   //得到開始、結束  
26     {  
27         cout<<*ite <<" ";  
28     }  
29     cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;  
30   
31     //尾部刪除;容量沒變【capacitty】,但是使用空間減少一個;容量一旦增加就不會減小;  
32     vec.pop_back();  
33     for(ite=vec.begin();ite!=vec.end();ite++)   //得到開始、結束  
34     {  
35         cout<<*ite <<" ";  
36     }  
37     cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;  
38   
39     vec.push_back(88);    
40     vec.push_back(99); //容量剛好夠;  
41   
42     for(ite=vec.begin();ite!=vec.end();ite++)   //得到開始、結束  
43     {  
44         cout<<*ite <<" ";  
45     }  
46     cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;  
47   
48     ite = find(vec.begin(),vec.end(),88);   //查找這個元素;  
49     vec.erase(ite);  //利用叠代器指針刪除這個元素;  
50     for(int i=0;i<vec.size();i++)   //size()是指容器裏當前有多少個使用的元素;  
51     {  
52         cout<<vec[i]<<" ";  
53     }  
54     cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;  //得到容器裏用的多少個空間,和總共的大小;  
55   
56     vec.clear(); //只是清除了數據,沒有回收空間,空間的等到對象的生命周期結束時回收;  
57     //使用空間為0,但是容量的空間還在,只有在調用析構函數的時候空間才會回收;  
58   
59     for(int i=0;i<vec.size();i++)   //size()是指容器裏當前有多少個使用的元素;  
60     {  
61         cout<<vec[i]<<"  ";  
62     }  
63     cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;  
64   
65     ite=find(vec.begin(),vec.end(),88);  
66     vec.insert(ite,2,77);  //叠代器標記的位置前,插入數據;  
67   
68     //cout<<*ite<<endl;  //會崩潰,因為叠代器在使用後就釋放了,*ite的時候就找不到它的地址了;  
69     //和向量的用法一樣,但是鏈表list不同,它的叠代器在使用後還可以繼續用;鏈表特有的;</span>  
70   
71     for(int i=0;i<vec.size();i++)     
72     {  
73         cout<<vec[i]<<"  ";  
74     }  
75     cout<<endl<<vec.size()<<" "<<vec.capacity()<<endl;  
76   
77     system("pause");  
78     return 0;  
79 }  

運行結果如下:

技術分享圖片

List代碼示例:

 1 #include<iostream>  
 2 #include <list>  
 3 #include <algorithm>  
 4 using namespace  std;  
 5 int main()  
 6 {  
 7     list<char> lit;   
 8     //用法和向量一樣,  
 9     //list是一個類模板,template,char是鏈表裏對象的類型,lit是創建的一個對象;  
10     //鏈表可以再頭尾兩端插入,是雙向的;  
11   
12     lit.push_back(a);  
13     lit.push_back(b);  
14     lit.push_front(d);  
15     lit.push_front(e);  
16     lit.push_front(f);  
17     lit.push_front(b);  
18     lit.push_front(b);  
19   
20     list<char>::iterator it;  //定義一個list的叠代器,類似一個紙箱鏈表的指針,但是比一般的指針好用,裏面用到了好多重載操作;  
21     list<char>::iterator it1;    
22     list<char>::iterator it2;    
23     for(it=lit.begin();it!=lit.end();it++)  
24     {  
25         cout<<*it<<"  ";  
26     }  
27     cout<<endl;  
28     //-----------鏈表可以從兩端刪除-------------------   
29     lit.pop_back();    
30     lit.pop_front();  
31     for(it=lit.begin();it!=lit.end();it++)  
32     {  
33         cout<<*it<<"  ";  
34     }  
35     cout<<endl;  
36     //-------------刪除所有的a---------------------------------  
37     //lit.remove(‘a‘);  //刪除所有的a;  
38   
39     for(it=lit.begin();it!=lit.end();it++)  
40     {  
41         cout<<*it<<"  ";  
42     }  
43     cout<<endl;  
44     //-------------移除連續且相同的a,只剩下一個;--------------------------------  
45     lit.unique();  //移除連續且相同的a,只剩下一個;  
46   
47     for(it=lit.begin();it!=lit.end();it++)  
48     {  
49         cout<<*it<<"  ";  
50     }  
51     cout<<endl;  
52     list<char> lit1;  
53     lit1.push_back(g);  
54     lit1.push_back(h);  
55     lit1.push_back(i);  
56     lit1.push_back(k);  
57     for(it1=lit1.begin();it1!=lit1.end();it1++)  
58     {  
59         cout<<*it1<<"  ";  
60     }  
61     cout<<endl;  
62     //-------------將一個鏈表插入到另一個鏈表---------------------------------  
63     it1=find(lit.begin(),lit.end(),f);  //先的找到要插入的位置,在該位置的前一個插入;  
64     ////lit.splice(it1,lit1); //將第二個鏈表插入到第一個鏈表中;合並後的鏈表就沒了,因為傳的是&;  
65     for(it=lit.begin();it!=lit.end();it++)  
66     {  
67         cout<<*it<<"  ";  
68     }  
69     cout<<endl;  
70     //------在鏈表lit中的it前插入lit1中的一個元素it1;在f之前插入k-----  
71     //-----拿下來之後那個元素就沒有了-------------------  
72     it=find(lit.begin(),lit.end(),f);  
73     it1=find(lit1.begin(),lit1.end(),k);  
74     lit.splice(it,lit1,it1);  
75     //-------------把鏈表中的一段插入到另一個鏈表中---------------------------------  
76     //把鏈表lit1中的[it-----it1)段的字符插入到lit的it2指針前;  
77     it=find(lit1.begin(),lit1.end(),h);  
78     it1=find(lit1.begin(),lit1.end(),k);  
79     it2=find(lit.begin(),lit.end(),f);  
80     lit.splice(it2,lit1,it,it1);   
81     // ----void merge(list& x); //將x合並到*this 身上。兩個lists 的內容都必須先經過遞增歸並排序。  
82     lit.sort();   //對兩個排序進行歸並排序;  
83     lit1.sort();  
84     lit.merge(lit1);  
85     //-----------將list裏的數據倒序排列---------------  
86     lit.reverse();  
87     for(it=lit.begin();it!=lit.end();it++)  
88     {  
89         cout<<*it<<"  ";  
90     }  
91     cout<<endl;  
92     for(it1=lit1.begin();it1!=lit1.end();it1++)  
93     {  
94         cout<<*it1<<"  ";  
95     }  
96     cout<<endl;  
97     system("pause");  
98     return 0;  
99 } 

運行結果:

技術分享圖片

再說下上面用到地List的兩個函數:uniqie()和splice();

1、uniqie():

  在STL中unique函數是一個去重函數, unique的功能是去除相鄰的重復元素(只保留一個),其實它並不真正把重復的元素刪除,是把重復的元素移到後面去了,然後依然保存到了原數組中,然後 返回去重後最後一個元素的地址,因為unique去除的是相鄰的重復元素,所以一般用之前都會要排一下序。  

unique返回的叠代器指向超出無重復的元素範圍末端的下一個位置。

註意:算法不直接修改容器的大小。如果需要添加或刪除元素,則必須使用容器操作。

 1 #include <iostream>
 2 #include <cassert>
 3 #include <algorithm>
 4 #include <vector>
 5 #include <string>
 6 #include <iterator>
 7  using namespace std;
 8 
 9  int main()
10 {
11     //cout<<"Illustrating the generic unique algorithm."<<endl;
12     const int N=11;
13     int array1[N]={1,2,0,3,3,0,7,7,7,0,8};
14     vector<int> vector1;
15     for (int i=0;i<N;++i)
16         vector1.push_back(array1[i]);
17 
18     vector<int>::iterator new_end;
19     new_end=unique(vector1.begin(),vector1.end());    //"刪除"相鄰的重復元素
20     assert(vector1.size()==N);
21 
22     vector1.erase(new_end,vector1.end());  //刪除(真正的刪除)重復的元素
23     copy(vector1.begin(),vector1.end(),ostream_iterator<int>(cout," "));
24     cout<<endl;
25 
26     return 0;
27 }

結果:1 2 0 3 0 7 0 8

unique_copy()

算法標準庫定義了一個名為unique_copy的函數,其操作類似於unique。

唯一的區別在於:前者接受第三個叠代器實參,用於指定復制不重復元素的目標序列。

unique_copy根據字面意思就是去除重復元素再執行copy運算。

編寫程序使用unique_copy將一個list對象中不重復的元素賦值到一個空的vector對象中。

 1 //使用unique_copy算法
 2 //將一個list對象中不重復的元素賦值到一個空的vector對象中
 3 #include<iostream>
 4 #include<list>
 5 #include<vector>
 6 #include<algorithm>
 7 using namespace std;
 8 
 9 int main()
10 {
11     int ia[7] = {5 , 2 , 2 , 2 , 100 , 5 , 2};
12     list<int> ilst(ia , ia + 7);
13     vector<int> ivec;
14 
15     //將list對象ilst中不重復的元素復制到空的vector對象ivec中
16     //sort(ilst.begin() , ilst.end());  //不能用此種排序,會報錯
17     ilst.sort();  //在進行復制之前要先排序,切記
18     unique_copy(ilst.begin() , ilst.end() , back_inserter(ivec));
19 
20     //輸出vector容器
21     cout<<"vector: "<<endl;
22     for(vector<int>::iterator iter = ivec.begin() ; iter != ivec.end() ; ++iter)
23         cout<<*iter<<" ";
24     cout<<endl;
25 
26     return 0;
27 }

假如

list<int> ilst(ia , ia + 7);
改為:vector<int> ilst(ia , ia + 7);

則排序時可用:

sort(ilst.begin() , ilst.end());

這裏要註意list和vector的排序用什麽方法。

Effective STL》裏這些話可能有用處:
item 31
  
  “我們總結一下你的排序選擇:
   ● 如果你需要在vector、string、deque或數組上進行完全排序,你可以使用sort或stable_sort。
   ● 如果你有一個vector、string、deque或數組,你只需要排序前n個元素,應該用partial_sort
   ● 如果你有一個vector、string、deque或數組,你需要鑒別出第n個元素或你需要鑒別出最前的n個元素,而不用知道它們的順序,nth_element是你應該註意和調用的。
   ● 如果你需要把標準序列容器的元素或數組分隔為滿足和不滿足某個標準,你大概就要找partition或stable_partition。
   ● 如果你的數據是在list中,你可以直接使用partition和stable_partition,你可以使用list的sort來代替sort和stable_sort。如果你需要partial_sort或nth_element提供的效果,你就必須間接完成這個任務,但正如我在上面勾畫的,會有很多選擇。
  
  另外,你可以通過把數據放在標準關聯容器中的方法以保持在任何時候東西都有序。你也可能會考慮標準非STL容器priority_queue,它也可以總是保持它的元素有序。

2、splice()

list::splice實現list拼接的功能。將源list的內容部分或全部元素刪除,拼插入到目的list。

函數有以下三種聲明:

void splice ( iterator position, list<T,Allocator>& x ); //

void splice ( iterator position, list<T,Allocator>& x, iterator i );

void splice ( iterator position, list<T,Allocator>& x, iterator first, iterator last );

函數說明:在list間移動元素:

將x的元素移動到目的list的指定位置,高效的將他們插入到目的list並從x中刪除。

目的list的大小會增加,增加的大小為插入元素的大小。x的大小相應的會減少同樣的大小。

前兩個函數不會涉及到元素的創建或銷毀。第三個函數會。

指向被刪除元素的叠代器會失效。

參考:https://blog.csdn.net/gogokongyin/article/details/51178378

  https://www.cnblogs.com/heyonggang/archive/2013/08/07/3243477.html

  https://blog.csdn.net/bichenggui/article/details/4674900

C++ vector、list和deque的區別 (整理)