更新日期:2019-09-11
概述
在使用容器之前,您應(yīng)該至少了解 C++
的基礎(chǔ)知識(shí),以及數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)知識(shí)(文內(nèi)就不描述數(shù)據(jù)結(jié)構(gòu)的內(nèi)容了)。文內(nèi)默認(rèn) C++
版本為 C++ 17
。
我們來(lái)看一下什么叫容器
容器:
儲(chǔ)存一系列元素的對(duì)象。每一個(gè)容器都管理其元素的存儲(chǔ)空間并允許開發(fā)者通過迭代器和成員函數(shù)訪問其內(nèi)部元素。C++
的 STL 容器 通常用于實(shí)現(xiàn)我們?cè)诔绦蛑薪?jīng)常使用到的數(shù)據(jù)結(jié)構(gòu),例如
- 棧
- 隊(duì)列/雙端隊(duì)列
- 鏈表
- 樹
- 關(guān)聯(lián)集合
- 動(dòng)態(tài)數(shù)組
在 C++
與 Java
等靜態(tài)類型語(yǔ)言中,被儲(chǔ)存的對(duì)象必須是同一類型。
分類:
-
序列容器:一種各元素之間有順序關(guān)系的線性表,是一種線性結(jié)構(gòu)的可序群集。
- 順序性容器中的每個(gè)元素均有固定的位置,除非用刪除或插入的操作改變這個(gè)位置。
- 順序容器的元素排列次序與元素值無(wú)關(guān),而是由元素添加到容器里的次序決定
序列容器適配器
-
關(guān)聯(lián)容器
- 關(guān)聯(lián)式容器是非線性的樹結(jié)構(gòu),更準(zhǔn)確的說(shuō)是二叉樹結(jié)構(gòu)。
- 各元素之間沒有嚴(yán)格的物理上的順序關(guān)系,也就是說(shuō)元素在容器中并沒有保存元素置入容器時(shí)的邏輯順序。
- 但是關(guān)聯(lián)式容器提供了另一種根據(jù)元素特點(diǎn)排序的功能,這樣迭代器就能根據(jù)元素的特點(diǎn)“順序地”獲取元素。
- 元素是有序的集合,默認(rèn)在插入的時(shí)候按升序排列(
set
,multiset
,map
,multimap
)!
無(wú)序關(guān)聯(lián)容器
序列容器
序列容器用作以線性方式存儲(chǔ)同一類型對(duì)象的數(shù)據(jù)結(jié)構(gòu)。
Vector
我們常常稱 vector
是動(dòng)態(tài)數(shù)組,基于數(shù)組實(shí)現(xiàn)。其在內(nèi)存中是一段連續(xù)的區(qū)域。何謂動(dòng)態(tài)?動(dòng)態(tài)就是說(shuō) vector
有自動(dòng)的內(nèi)存管理功能。可以動(dòng)態(tài)的改變vector的長(zhǎng)度,并隨著元素的增加與減小來(lái)自動(dòng)改變數(shù)組大小,它提供了直接添加尾部元素或者刪除元素的方法!所以它的時(shí)間是固定的!然而,他要在頭部與中間插入或者刪除元素是線性的時(shí)間復(fù)雜度!
特點(diǎn):他可以反轉(zhuǎn)序列,所以它可以反向遍歷可反轉(zhuǎn)序列!(基于他的rbegin,rend)
定義與初始化:
要調(diào)用頭文件
#include<vector>
定義與初始化:
vector<int> v;//默認(rèn)初始化
vector<int> v(v1);//用v1初始化v
vector<int> v(v1.begin(),v1.end());//用v1初始化v
vector<int> v(10);//定義一個(gè)大小為10的數(shù)組!
vector<int> v(10,1)//定義個(gè)全為1而且長(zhǎng)度為10的數(shù)組
方法:
a.front(),a.rbegin() //首元素
a.back(),a.rend() //末尾元素
v.push_back() //增
v.insert() //由于insert重載方法比較多
// 1.v.insert(p,t)//將t插到p的前面
// 2.v.insert(p,n,t)//將n個(gè)t插入p之前
// 3.v.insert(p,i.j)//將區(qū)間[i,j)的元素插入到p之前
v.pop_back() //刪
v.erase(t,k)
// 1.v.erase(t,k)//刪除他們之間的元素
// 2.v.erase(p)//刪除p指向的元素
v.chear===v.erase(begin(),end());
遍歷
//下標(biāo)法
for(int i = 0; i < v.size(); i++) {
std::cout << v[i];
}
//迭代器法
for(vector<int>::const_iterator iterator = v.begin();
iterator != v.end();
iterator++) {
std::cout << *iterator;
}
// 上述代碼在現(xiàn)代 C++ 中可以使用 auto 關(guān)鍵字
//迭代器法
for(auto iterator = v.begin();
iterator != v.end();
iterator++) {
std::cout << *iterator;
}
// 甚至,可以使用范圍循環(huán)
for(auto it : v) {
std::cout << *it;
}
Array
數(shù)組 Array
是靜態(tài)連續(xù)數(shù)組。他的長(zhǎng)度固定,使用沒有調(diào)節(jié)大小的操作,但是他有一些有意義的成員函數(shù),如operator[] 和at(),當(dāng)然有很多STL算法用于array,如copy(),for_each()。我們后面會(huì)詳細(xì)介紹一些C++知識(shí)點(diǎn)!
/**
* 導(dǎo)入頭文件
*/
#include <array>
deque
雙端隊(duì)列,他的實(shí)現(xiàn)類\似與vector,支持隨機(jī)訪問,但是它訪問首元素的插入(push_front())與刪除(pop_front())的時(shí)間是固定的!而且他的執(zhí)行速度要比vector快很多!所以題目中有大量的操作發(fā)生在序列的起始位置與結(jié)尾處,我們就要考慮用deque!
調(diào)用頭文件:
#include<deque>
初始化與定義已經(jīng)在序列要求里面,而且方法與vector類似,只是多了push_front()
, pop_front()
,我們就不做過多的闡述
list
雙向鏈表,list在鏈表中的任意一個(gè)位置插入與刪除一個(gè)元素時(shí)間是固定的!但是他不能隨機(jī)訪問,優(yōu)點(diǎn)是元素的快速插入與刪除!從容器中插入與刪除元素之后i,迭代器指向元素將不變,不會(huì)移動(dòng)已有元素,只是修改鏈表信息。
#include<list>
我們來(lái)看一下他的鏈表獨(dú)有成員函數(shù)!
void sort() //使用<運(yùn)算符對(duì)鏈表進(jìn)行排序,時(shí)間復(fù)雜度O(NlogN)
void merge(list<T,Alloc>&x) //將x與調(diào)用鏈表合并,要求:兩個(gè)鏈表必須要已經(jīng)排好序!元素將保存在調(diào)用鏈表中,x為空,這個(gè)時(shí)間復(fù)雜度為線性!
void remove(const T &val)//刪除val的所有實(shí)例
void splice(iterator pos,list<T,Alloc>x)//將鏈表x的內(nèi)容加到pos的前面,時(shí)間復(fù)雜度為固定時(shí)間
void unique() //去重,線性時(shí)間
forword_list
參加noip的同學(xué)注意了!他是C++11新添加的容器類!它主要實(shí)現(xiàn)了單向鏈表,只需要正向迭代器,他是不可逆轉(zhuǎn)容器,他功能比較少,但是它比較簡(jiǎn)單!
序列容器適配器
stack
適配器,它可以將任意類型的序列容器轉(zhuǎn)換為一個(gè)堆棧,一般使用deque作為支持的序列容器。元素只能后進(jìn)先出(LIFO)。不能遍歷整個(gè)stack。他給vector提供了棧接口!
與queue類似,如果要使用棧中元素,先用top檢索,然后用pop將他從棧中刪除,這個(gè)太過于常用不介紹方法!
queuue
他是一個(gè)配適器類,ostream_iterator就是一個(gè)配適器,讓輸出流能夠使用迭代器接口,同樣它實(shí)現(xiàn)了隊(duì)列接口!它不僅不允許隨機(jī)訪問元素,而且還不能遍歷隊(duì)列!元素只能先進(jìn)先出(FIFO).
方法:
bool empty()//判斷是否為空
front()//隊(duì)首元素的訪問
back()//隊(duì)尾元素的訪問
push(x)//隊(duì)尾插入x
pop()//刪除隊(duì)首元素
priority_queue
另一個(gè)配適器,他與queue基本一樣,但是他的最大元素被移動(dòng)到隊(duì)首(生活不總是公平對(duì),隊(duì)列也一樣),內(nèi)部區(qū)別在于底層結(jié)構(gòu)不一樣,他用的是vector,當(dāng)然我們可以修改確定拿個(gè)元素放在隊(duì)首的比較方式!
priority_queue<int> X //大根堆,默認(rèn)初始化
priority_queue<int, vector<int>, greater<int>> x //小根堆,運(yùn)用了預(yù)定義函數(shù)greater<int>!
以下內(nèi)容摘自C++API:
包含priority_queue 的頭文件是 <queue>
priority_queue類的主要成員:
priority_queue(); //默認(rèn)構(gòu)造函數(shù),生成一個(gè)空的排序隊(duì)列
priority_queue(const queue&); //拷貝構(gòu)造函數(shù)
priority_queue& operator=(const priority_queue &); //賦值運(yùn)算符重載
priority_queue 的私有成員:
value_type; //priority_queue中存放的對(duì)象類型,它和priority_queue中的T類型相同
priority_queue(const Compare& comp); //構(gòu)造生成一個(gè)空的priority_queue對(duì)象,使用comp作為priority_queue的comparison
priority_queue(const value_type* first, const value_type* last); //帶有兩個(gè)參數(shù)的構(gòu)造 函數(shù),使用默認(rèn)的Comparison作為第三個(gè)參數(shù)
size_type; //正整數(shù)類型,和Sequence::size_type類型一樣。
bool empty() const; //判斷優(yōu)先級(jí)隊(duì)列是否為空,為空返回true,否則返回false
size_type size() const; //返回優(yōu)先級(jí)隊(duì)列中的元素個(gè)數(shù)
const value_type& top() const(); //返回優(yōu)先級(jí)隊(duì)列中第一個(gè)元素的參考值。
void push(const value_type& x); //把元素x插入到優(yōu)先級(jí)隊(duì)列的尾部,隊(duì)列的長(zhǎng)度加1
void pop(); //刪除優(yōu)先級(jí)隊(duì)列的第一個(gè)值,前提是隊(duì)列非空,刪除后隊(duì)列長(zhǎng)度減1
關(guān)聯(lián)容器
它運(yùn)用了鍵值對(duì)(value-key),與java類似的map,例如hashmap,有點(diǎn)在于他提供了利用key快速訪問功能,它的底層結(jié)構(gòu)應(yīng)該是一種樹來(lái)實(shí)現(xiàn)的,所以他才有如此快的查找速度,最簡(jiǎn)單的set,他的鍵值對(duì)類型是一致的,而且唯一,元素默認(rèn)按升序排列。map他的鍵值對(duì)類型不同,鍵是唯一的,元素默認(rèn)按鍵的升序排列。!而muilti_sset/map 鍵可以不唯一。
迭代器在關(guān)聯(lián)容器中對(duì)操作:
m.lower_bound(k)//返回一個(gè)迭代器,指向鍵不小于 k 的第一個(gè)元素
m.upper_bound(k)//返回一個(gè)迭代器,指向鍵大于 k 的第一個(gè)元素
m.equal_range(k)//返回一個(gè)迭代器的 pair 對(duì)象。它的 first 成員等價(jià)于m.lower_bound(k) //。而 second 成員則等價(jià)于 m.upper_bound(k)
map
map 是鍵-值對(duì)的集合。map 類型通常可理解為關(guān)聯(lián)數(shù)組:可使用鍵作為下標(biāo)來(lái)獲取一個(gè)值,正如內(nèi)置數(shù)組類型一樣。而關(guān)聯(lián)的本質(zhì)在于元素的值與某個(gè)特定的鍵相關(guān)聯(lián),而并非通過元素在數(shù)組中的位置來(lái)獲取。
定義與初始化
map<int,string> map1; //默認(rèn)為空
m.insert()
// 1.m.insert(e)//e是一個(gè)用在m上的value_kry 類型的值。如果鍵(e.first不在m中,則插入一個(gè)值為e.second 的新元素;如果該鍵在m中已存在,則保持m不變。該函數(shù)返回一個(gè)pair類型對(duì)象,包含指向鍵為e.first的元素的map迭代器,以及一個(gè) bool 類型的對(duì)象,表示是否插入了該元素
// 2.m.insert(begin,end)//begin和end是標(biāo)記元素范圍的迭代器,其中的元素必須為m.value_key 類型的鍵-值對(duì)。對(duì)于該范圍內(nèi)的所有元素,如果它的鍵在 m 中不存在,則將該鍵及其關(guān)聯(lián)的值插入到 m。返回 void 類型
// 3.m.insert(iter,e)//e是一個(gè)用在m上的 value_key 類型的值。如果鍵(e.first)不在m中,則創(chuàng)建新元素,并以迭代器iter為起點(diǎn)搜索新元素存儲(chǔ)的位置。返回一個(gè)迭代器,指向m中具有給定鍵的元素
m.count(k) //返回m中k的出現(xiàn)次數(shù)
m.find() //如果m容器中存在按k索引的元素,則返回指向該元素的迭代器。如果不存在,則返回超出末端迭代器.
m.erase() //具體與序列該方法一致!
set
支持插入,刪除,查找等操作,就像一個(gè)集合一樣。所有的操作的都是嚴(yán)格在logn時(shí)間之內(nèi)完成,效率非常高。set和multiset的區(qū)別是:set插入的元素不能相同,但是multiset可以相同。Set默認(rèn)自動(dòng)排序。使用方法類似list。
set容器的定義和使用
set 容器的每個(gè)鍵都只能對(duì)應(yīng)一個(gè)元素。以一段范圍的元素初始化set對(duì)象,或在set對(duì)象中插入一組元素時(shí),對(duì)于每個(gè)鍵,事實(shí)上都只添加了一個(gè)元素。
vector<int> ivec;
for(vector<int>::size_type i = 0; i != 10; ++i) {
ivec.push_back(i);
ivec.push_back(i);
}
set<int> iset(ivec.begin(), ivec.end());
cout << ivec.size() << endl;//20個(gè)
cout << iset.size() << endl;// 10個(gè)
添加
set<string> set1;
set1.insert("the"); //第一種方法:直接添加
set<int> iset2;
iset2.insert(ivec.begin(), ivec.end());//第二中方法:通過指針迭代器
獲取:
set<int> iset;
for(int i = 0; i<10; i++)
iset.insert(i);
iset.find(1)// 返回指向元素內(nèi)容為1的指針
iset.find(11)// 返回指針iset.end()
iset.count(1)// 存在,返回1
iset.count(11)// 不存在,返回0
由于其他兩個(gè)不常用我們不做過多介紹!有興趣的童鞋可以去CPPAPI或者 CPP底層源碼參考學(xué)習(xí)!
無(wú)序關(guān)聯(lián)容器
- unordered_set
- unordered_multiset
- unordered_map
- unordered_multimap
底層結(jié)構(gòu)基于哈希表,主要與提高添加與刪除元素得速度與提高查找算法得效率!無(wú)序關(guān)聯(lián)容器(unordered_set、unordered_multiset、unordered_map和 unordered_multimap)使用鍵和哈希表,以便能夠快速存取數(shù)據(jù)。
下面簡(jiǎn)要地介紹這些概念。哈希函數(shù)(hash function)將鍵轉(zhuǎn)換為索引值。例如,如果鍵為string對(duì)象,哈希函數(shù)可能將其中每個(gè)字符的數(shù)字編碼相加,再計(jì)算結(jié)果除以13的余數(shù),從而得到 一個(gè)0~12的索引。
而無(wú)序容器將使用13個(gè)桶(bucket)來(lái)存儲(chǔ)string,所有索引為4的string都將存儲(chǔ)在第4個(gè)桶中。如果您要在容器中搜索鍵,將對(duì)鍵執(zhí)行哈希函數(shù),進(jìn)而只在索引對(duì)應(yīng)的桶中搜索。理想情況下,應(yīng)有足夠多的桶,每個(gè)桶只包含為數(shù)不多的string。
C++11庫(kù)提供了模板hash<key>
,無(wú)序關(guān)聯(lián)容器默認(rèn)使用該模板。為各種整型、浮點(diǎn)型、指針以及一些模板類(如string)定義了該模板的具體化。
X(n, hf, eq)//創(chuàng)建一個(gè)至少包含n個(gè)桶的空容器,并將hf用作哈希函數(shù),將eq用作鍵值相等謂詞。如果省略了eq,則將ke
y_equal( )用作鍵值相等謂詞;如果也省略了hf,則將hasher( )用作哈希函數(shù)
X a(n, hf, eq)//創(chuàng)建一個(gè)名為a的空容器,它至少包含n個(gè)桶,并將hf用作哈希函數(shù),將eq用作鍵值相等謂詞。如果省略eq,則將key_equal( )用作鍵值相等謂詞;如果也省略了hf,則將hasher( )用作哈希函數(shù)
X(i, j, n, hf, eq)//創(chuàng)建一個(gè)至少包含n個(gè)桶的空容器,將hf用作哈希函數(shù),將eq用作鍵值相等謂詞,并插入?yún)^(qū)間[i, j]中的元素。如果省略了eq,將key_equal( )用作鍵值相等謂詞;如果省略了hf,將hasher( )用作哈希函數(shù);如果省略了n,則包含桶數(shù)不確定
X a(i, j, n, hf, eq)//創(chuàng)建一個(gè)名為a的的空容器,它至少包含n個(gè)桶,將hf用作哈希函數(shù),將eq用作鍵值相等謂詞,并插入?yún)^(qū)間[i, j]中的元素。如果省略了eq,將key_equal( )用作鍵值相等謂詞;如果省略了hf,
將hasher( )用作哈希函數(shù);如果省略了n,則包含桶數(shù)不確定
b.hash_function( )//返回b使用的哈希函數(shù)
b.key_eq( )//返回創(chuàng)建b時(shí)使用的鍵值相等謂詞
b.bucket_count( )//返回b包含的桶數(shù)
b.max_bucket_count ( )//返回一個(gè)上限數(shù),它指定了b最多可包含多少個(gè)桶
b.bucket(k)//返回鍵值為k的元素所屬桶的索引
b.bucket_size(n)//返回索引為n的桶可包含的元素?cái)?shù)
b.begin(n)//返回一個(gè)迭代器,它指向索引為n的桶中的第一個(gè)元素
b.end(n)//返回一個(gè)迭代器,它指向索引為n的桶中的最后一個(gè)元素
b.cbegin(n)//返回一個(gè)常量迭代器,它指向索引為n的桶中的第一個(gè)元素
b.cend(n)//返回一個(gè)常量迭代器,它指向索引為n的桶中的最后一個(gè)元素
b.load_factor()//返回每個(gè)桶包含的平均元素?cái)?shù)
b.max_load_factor()//返回負(fù)載系數(shù)的最大可能取值;超過這個(gè)值后,容器將增加桶
b.max_load_factor(z)//可能修改最大負(fù)載系統(tǒng),建議將它設(shè)置為z
a.rehash(n)//將桶數(shù)調(diào)整為不小于n,并確保a.bucket_count( )> a.size( ) / a.max_load_factor( )
a.reserve(n)//等價(jià)于a.rehash(ceil(n/a.max_load_factor( ))),
其中ceil(x)返回不小于x的最小整數(shù)
用法基本都是一致,所以我在這里給大家一個(gè)可以尋找到一些數(shù)據(jù)結(jié)構(gòu)的方法的路徑!故沒有給大家分析與解讀他們的一些存儲(chǔ)與方法作用圖用于理解!
小結(jié)
我們一般在用一些基本的數(shù)據(jù)結(jié)構(gòu)的時(shí)候,為了方便與解題技巧我們一般會(huì)用到容器!當(dāng)然具體方法太多,所以建議用什么學(xué)什么!
- 有序容器(除了list):存儲(chǔ)底層vector,只是添加了不同的接口!
- deque(隊(duì)列):它不像vector 把所有的對(duì)象保存在一塊連續(xù)的內(nèi)存塊,而是采用多個(gè)連續(xù)的存儲(chǔ)塊,并且在一個(gè)映射結(jié)構(gòu)中保存對(duì)這些塊及其順序的跟蹤。向deque 兩端添加或刪除元素的開銷很小,它不需要重新分配空間。
- list(列表):是一個(gè)線性鏈表結(jié)構(gòu),它的數(shù)據(jù)由若干個(gè)節(jié)點(diǎn)構(gòu)成,每一個(gè)節(jié)點(diǎn)都包括一個(gè)信息塊(即實(shí)際存儲(chǔ)的數(shù)據(jù))、一個(gè)前驅(qū)指針和一個(gè)后驅(qū)指針。它無(wú)需分配指定的內(nèi)存大小且可以任意伸縮,這是因?yàn)樗鎯?chǔ)在非連續(xù)的內(nèi)存空間中,并且由指針將有序的元素鏈接起來(lái)。
- 后面的關(guān)聯(lián)與無(wú)序關(guān)聯(lián)都是用的一種樹狀結(jié)構(gòu)!
用法與選擇:
- 當(dāng)數(shù)組大小未知時(shí),和需要高效的查詢功能,用vector!高效地隨機(jī)存儲(chǔ)。
- 不使用連續(xù)的內(nèi)存空間,而且可以隨意地進(jìn)行動(dòng)態(tài)操作,有大量的插入、刪除操作,用list!
- 需要在兩端進(jìn)行push 、pop用daque!它兼顧了數(shù)組和鏈表的優(yōu)點(diǎn),它是分塊的鏈表和多個(gè)數(shù)組的聯(lián)合。所以它有被list 好的查詢性能,有被vector 好的插入、刪除性能。 如果你需要隨即存取又關(guān)心兩端數(shù)據(jù)的插入和刪除,那么deque 是最佳之選。
- 需要查找或者打表可以選擇map與set,當(dāng)然一定條件下我們可以優(yōu)秀考慮用無(wú)序關(guān)聯(lián)容器!