史上最全的C++ STL 容器大禮包
為什么\(C++\)比\(C\)更受人歡迎呢?除了\(C++\) 的編譯令人感到更舒適,\(C++\)的標准模板庫(\(STL\))也占了很重要的原因。當你還在用手手寫快排、手寫二叉堆,挑了半天挑不出毛病的時候,\(C++\)黨一手\(STL\)輕松\(AC\),想不嫉妒都難。
所以這篇隨筆就帶大家走進博大精深的\(C++STL\),系統講解各種\(STL\)容器及其用法、作用。在學習\(STL\)的時候認真體會\(STL\)語法及功能,提升自己在算法競賽及程序設計中解題、碼代碼的能力。
話不多說,現在開始:
淺談C++ STL vector 容器
本篇隨筆簡單介紹一下\(C++STL\)中\(vector\)容器的使用方法和常見的使用技巧。\(vector\)容器是\(C++STL\)的一種比較基本的容器。我們在學習這個容器的時候,不僅要學到這個容器具體的使用方法,更要從中體會\(C++STL\)的概念。
vector容器的概念
\(vector\)在英文中是矢量的意思。如果學過高中數學必修四的平面向量或者高中物理必修一的第一節課對其會有一個直觀的認識。但是在\(STL\)中,\(vector\)和物理、幾何等東西沒有任何關系。
我們知道,一個數組必須要有固定的長度,在開一個數組的時候,這個長度也就被靜態地確定下來了。但是\(vector\)卻是數組的“加強版”,對於一組數據來講,你往\(vector\)里存多少數據,\(vector\)的長度就有多大。也就是說,我們可以將其理解為一個“變長數組”。
事實上,\(vector\)的實現方式是基於倍增思想的:假如\(vector\)的實際長度為\(n\),\(m\)為\(vector\)當前的最大長度,那么在加入一個元素的時候,先看一下,假如當前的\(n=m\),則再動態申請一個\(2m\)大小的內存。反之,在刪除的時候,如果\(n\ge \frac{m}{2}\),則再釋放一半的內存。
vector容器的聲明
\(vector\)容器存放在模板庫:#include<vector>
里,使用前需要先開這個庫。
\(vector\)容器的聲明遵循\(C++STL\)的一般聲明原則:
容器類型<變量類型> 名稱
例:
#include<vector>
vector<int> vec;
vector<char> vec;
vector<pair<int,int> > vec;
vector<node> vec;
struct node{...};
vector容器的使用方法
\(vector\)容器的使用方法大致如下表所示:
用法 | 作用 |
---|---|
vec.begin(),vec.end() |
返回vector的首、尾迭代器 |
vec.front(),vec.back() |
返回vector的首、尾元素 |
vec.push_back() |
從vector末尾加入一個元素 |
vec.size() |
返回vector當前的長度(大小) |
vec.pop_back() |
從vector末尾刪除一個元素 |
vec.empty() |
返回vector是否為空,1為空、0不為空 |
vec.clear() |
清空vector |
除了上面說過的那些之外,我們的\(vector\)容器是支持隨機訪問的,即可以像數組一樣用\([\,\,]\)來取值。請記住,不是所有的\(STL\)容器都有這個性質!在\(STL\)的學習過程中,一定要清楚各個容器之間的異同!
淺談C++ STL queue 容器
本篇隨筆簡單介紹一下\(C++STL\)中\(queue\)容器的使用方法和常見的使用技巧。\(queue\)容器是\(C++STL\)的一種比較基本的容器。我們在學習這個容器的時候,不僅要學到這個容器具體的使用方法,更要從中體會\(C++STL\)的概念。
queue容器的概念
\(queue\)在英文中是隊列的意思。隊列是一種基本的數據結構。而\(C++STL\)中的隊列就是把這種數據結構模板化了。我們可以在腦中想象買票時人們站的排隊隊列。我們發現,在一個隊列中,只可以從隊首離開,從隊尾進來(沒有插隊,想啥呢)。即一個先進先出的數據結構。
上圖理解:
queue容器的聲明
\(queue\)容器存放在模板庫:#include<queue>
里,使用前需要先開這個庫。
\(queue\)容器的聲明遵循\(C++STL\)的一般聲明原則:
容器類型<變量類型> 名稱
例:
#include<queue>
queue<int> q;
queue<char> q;
queue<pair<int,int> > q;
queue<node> q;
struct node{...};
queue容器的使用方法
\(queue\)容器的使用方法大致如下表所示:
用法 | 作用 |
---|---|
q.front(),q.back() |
返回queue的首、尾元素 |
q.push() |
從queue末尾加入一個元素 |
q.size() |
返回queue當前的長度(大小) |
q.pop() |
從queue末尾刪除一個元素 |
q.empty() |
返回queue是否為空,1為空、0不為空 |
注意,雖然\(vector\)和\(queue\)是兩種最基本的\(STL\)容器,但請記住它們兩個不是完全一樣的。就從使用方法來講:
\(queue\)不支持隨機訪問,即不能像數組一樣地任意取值。並且,\(queue\)並不支持全部的\(vector\)的內置函數。比如\(queue\)不可以用\(clear()\)函數清空,清空\(queue\)必須一個一個彈出。同樣,\(queue\)也並不支持遍歷,無論是數組型遍歷還是迭代器型遍歷統統不支持,所以沒有\(begin(),end();\)函數,使用的時候一定要清楚異同!
淺談C++ STL stack 容器
本篇隨筆簡單介紹一下\(C++STL\)中\(stack\)容器的使用方法和常見的使用技巧。
stack容器的概念
\(stack\)在英文中是棧的意思。棧是一種基本的數據結構。而\(C++STL\)中的棧就是把這種數據結構模板化了。
棧的示意圖如下:這是一個先進后出的數據結構。這非常重要!!
事實上,\(stack\)容器並不是一種標准的數據結構,它其實是一個容器適配器,里面還可以存其他的\(STL\)容器。但那種使用方法過於高深而且不是很常用,所以在此不與介紹。請有興趣的讀者自行查詢資料。
stack容器的聲明
\(stack\)容器存放在模板庫:#include<stack>
里,使用前需要先開這個庫。
\(stack\)容器的聲明遵循\(C++STL\)的一般聲明原則:
容器類型<變量類型> 名稱
例:
#include<stack>
stack<int> st;
stack<char> st;
stack<pair<int,int> > st;
stack<node> st;
struct node{...};
stack容器的使用方法
\(stack\)容器的使用方法大致如下表所示:
用法 | 作用 |
---|---|
st.top() |
返回stack的棧頂元素 |
st.push() |
從stack棧頂加入一個元素 |
st.size() |
返回stack當前的長度(大小) |
st.pop() |
從stack棧頂彈出一個元素 |
st.empty() |
返回stack是否為空,1為空、0不為空 |
淺談C++ STL string容器
本篇隨筆簡單講解一下\(C++STL\)中\(string\)容器的使用方法及技巧。
string容器的概念
其實\(string\)並不是\(STL\)的一種容器,但是由於它的使用方法等等和\(STL\)容器很像,所以就把它當作\(STL\)容器一樣介紹。
其實\(string\)容器就是個字符串,這通過它的英文譯名就能看得出來。但是對於字符串以及字符串的相關操作,可能讀者還是對普通的\(C/C++\)的#include<cstring>
,#include<string.h>
庫更熟悉一些。我絲毫不否認這些傳統字符操作的經典性和實用性,但是由於它們函數定義的局限,有些時候對於一些特殊的讀入、輸出、遍歷等要求,它的操作並不如\(string\)容器好用。
比如,要求讀入一群中間可能帶空格的字符串,如果用傳統方式進行讀入,可能就會很麻煩,但是如果使用\(string\)的話,一個讀入函數就可以完全搞定。
string容器的使用方法及與傳統字符讀入的對比
一張圖解決問題。
詳解C++ STL priority_queue 容器
本篇隨筆簡單介紹一下\(C++STL\)中\(priority_queue\)容器的使用方法和常見的使用技巧。
priority_queue容器的概念
\(priority_queue\)在英文中是優先隊列的意思。
隊列是一種基本的數據結構。其實現的基本示意圖如下所示:
而\(C++STL\)中的優先隊列就是在這個隊列的基礎上,把其中的元素加以排序。其內部實現是一個二叉堆。所以優先隊列其實就是把堆模板化,將所有入隊的元素排成具有單調性的一隊,方便我們調用。
priority_queue容器的聲明
\(priority_queue\)容器存放在模板庫:#include<queue>
里,使用前需要先開這個庫。
這里需要注意的是,優先隊列的聲明與一般\(STL\)模板的聲明方式並不一樣。事實上,我認為其是\(C++STL\)中最難聲明的一個容器。
大根堆聲明方式:
大根堆就是把大的元素放在堆頂的堆。優先隊列默認實現的就是大根堆,所以大根堆的聲明不需要任何花花腸子,直接按\(C++STL\)的聲明規則聲明即可。
#include<queue>
priority_queue<int> q;
priority_queue<string> q;
priority_queue<pair<int,int> > q;
\(C++\)中的\(int,string\)等類型可以直接比較大小,所以不用我們多操心,優先隊列自然會幫我們實現。但是如果是我們自己定義的結構體,就需要進行重載運算符了。關於重載運算符的講解,請參考這篇博客:
小根堆聲明方式
大根堆是把大的元素放堆頂,小根堆就是把小的元素放到堆頂。
實現小根堆有兩種方式:
第一種是比較巧妙的,因為優先隊列默認實現的是大根堆,所以我們可以把元素取反放進去,因為負數的絕對值越小越大,那么絕對值較小的元素就會被放在前面,我們在取出的時候再取個反,就瞞天過海地用大根堆實現了小根堆。
第二種:
小根堆有自己的聲明方式,我們記住即可(我也說不明白道理):
priority_queue<int,vector<int>,greater<int> >q;
注意,當我們聲明的時候碰到兩個"<"或者">"放在一起的時候,一定要記得在中間加一個空格。這樣編譯器才不會把兩個連在一起的符號判斷成位運算的左移/右移。
priority_queue容器的使用方法
\(priority_queue\)容器的使用方法大致如下表所示:
用法 | 作用 |
---|---|
q.top() |
返回priority_queue的首元素 |
q.push() |
向priority_queue中加入一個元素 |
q.size() |
返回priority_queue當前的長度(大小) |
q.pop() |
從priority_queue末尾刪除一個元素 |
q.empty() |
返回priority_queue是否為空,1為空、0不為空 |
注意:priority_queue取出隊首元素是使用\(top\),而不是\(front\),這點一定要注意!!
淺談C++ STL deque 容器
本篇隨筆簡單介紹一下\(C++STL\)中\(deque\)容器的使用方法及常見使用技巧。
deque容器的概念
\(deque\)的意義是:雙端隊列。隊列是我們常用而且必須需要掌握的數據結構。\(C++STL\)中的確有模擬隊列的模板:#include<queue>
中的\(queue\)和\(priority\_queue\)。隊列的性質是先進先出,即從隊尾入隊,從隊首出隊。而\(deque\)的特點則是雙端進出,即處於雙端隊列中的元素既可以從隊首進/出隊,也可以從隊尾進/出隊。
即:\(deque\)是一個支持在兩端高效插入、刪除元素的線性容器。
\(deque\)模板存儲在\(C++STL\)的#include<deque>
中。
deque容器的使用方法
因為\(deque\)容器真的和\(queue\)容器大體相同,其使用方式也大體一致。下面把\(deque\)容器的使用方式以列表的方式放在下面:
用法 | 作用 |
---|---|
q.begin(),q.end() |
返回deque的首、尾迭代器 |
q.front(),q.back() |
返回deque的首、尾元素 |
q.push_back() |
從隊尾入隊一個元素 |
q.push_front() |
從隊頭入隊一個元素 |
q.pop_back() |
從隊尾出隊一個元素 |
q.pop_front() |
從隊頭出隊一個元素 |
q.clear() |
清空隊列 |
除了這些用法之外,\(deque\)比\(queue\)更優秀的一個性質是它支持隨機訪問,即可以像數組下標一樣取出其中的一個元素。
即:q[i]
。
deque的一些用途
由於本蒟蒻水平有限,暫時想不出deque應用的一些實例。但有一點是肯定的:\(deque\)容器可以被應用到\(SPFA\)算法的\(SLF\)優化。其具體應用方式可見這篇博客:
詳解C++ STL set 容器
本篇隨筆簡單介紹一下\(C++STL\)中\(set\)容器的使用方法及常見使用技巧。
set容器的概念和性質
\(set\)在英文中的意義是:集合。\(set\)容器也的確“人如其名”,實現了這個集合的功用。
高中數學必修一集合那章(高一以下的小伙伴不用慌,不講數學只講概念),關於集合的性質,給出了三個概念:無序性、互異性、確定性。
那么,\(set\)容器的功用就是維護一個集合,其中的元素滿足互異性。
我們可以將其理解為一個數組。這個數組的元素是兩兩不同的。
這個兩兩不同是指,如果這個\(set\)容器中已經包含了一個元素\(i\),那么無論我們后續再往里假如多少個\(i\),這個\(set\)中還是只有一個元素\(i\),而不會出現一堆\(i\)的情況。這就為我們提供了很多方便。
但是,需要額外說明的是,剛剛說集合是有無序性的,但是\(set\)中的元素是默認排好序(按升序排列)的。(稍微說一句,\(set\)容器自動有序和快速添加、刪除的性質是由其內部實現:紅黑樹(平衡樹的一種)。這個東西過於高深我不會,所以不予過多介紹,有興趣的小伙伴可以自行瀏覽相關內容。)
set容器的聲明
\(set\)容器的聲明和大部分\(C++STL\)容器一樣,都是:容器名<變量類型> 名稱的結構。前提需要開#include
#include<set>
set<int> s;
set<char> s;
set<pair<int,int> > s;
set<node> s;
struct node{...};
set容器的使用
其實,\(C++STL\)容器的使用方式都是差不多的。我們完全可以舉一反三地去類比。與\(bitset\)重定義了許多奇形怪狀新的函數之外,其他都是大致相同的。所以筆者在此不再做幼稚的介紹,大家都是競賽狗,應該都能自己看明白。
s.empty();
\(empty()\)函數返回當前集合是否為空,是返回1,否則返回0.
s.size();
\(size()\)函數返回當前集合的元素個數。
s.clear();
\(clear()\)函數清空當前集合。
s.begin(),s.end();
\(begin()\)函數和\(end()\)函數返回集合的首尾迭代器。注意是迭代器。我們可以把迭代器理解為數組的下標。但其實迭代器是一種指針。這里需要注意的是,由於計算機區間“前閉后開”的結構,\(begin()\)函數返回的指針指向的的確是集合的第一個元素。但\(end()\)返回的指針卻指向了集合最后一個元素后面一個元素。
s.insert(k);
\(insert(k)\)函數表示向集合中加入元素\(k\)。
s.erase(k);
\(erase(k)\)函數表示刪除集合中元素\(k\)。這也反映了\(set\)容器的強大之處,指哪打哪,說刪誰就刪誰,完全省略了遍歷、查找、復制、還原等繁瑣操作。更不用像鏈表那種數據結構那么毒瘤。直接一個函數,用\(O(logn)\)的復雜度解決問題。
s.find(k);
\(find(k)\)函數返回集合中指向元素\(k\)的迭代器。如果不存在這個元素,就返回\(s.end()\),這個性質可以用來判斷集合中有沒有這個元素。
其他好用的函數
下面介紹一些不是很常用,但是很好用的\(set\)容器的內置函數
s.lower_bound(),s.upper_bound();
熟悉\(algorithm\)庫和二分、離散化的小伙伴會對這兩個函數比較熟悉。其實這兩個函數比較常用。但是對於\(set\)集合來講就不是很常用。其中\(lower\_bound\)返回集合中第一個大於等於關鍵字的元素。\(upper\_bound\)返回集合中第一個嚴格大於關鍵字的元素。
s.equal_range();
這個東西是真的不常用...可能是我太菜了。
這個東西返回一個\(pair\)(內置二元組),分別表示第一個大於等於關鍵字的元素,第一個嚴格大於關鍵字的元素,也就是把前面的兩個函數和在一起。如果有一個元素找不到的話,就會返回\(s.end()\)。
詳解C++ STL multiset 容器
本篇隨筆簡單介紹一下\(C++STL\)中\(multiset\)容器的使用方法及常見使用技巧。
multiset容器的概念和性質
\(set\)在英文中的意義是:集合。而\(multi-\)前綴則表示:多重的。所以\(multiset\)容器就叫做:有序多重集合。
\(multiset\)的很多性質和使用方式和\(set\)容器差不了多少。而\(multiset\)容器在概念上與\(set\)容器不同的地方就是:\(set\)的元素互不相同,而\(multiset\)的元素可以允許相同。
所以,關於一些\(multiset\)容器和\(set\)容器的相同點,本篇博客就不加以贅述了。需要學習的小伙伴推薦進入本蒟蒻的這篇博客:
與set容器不太一樣的地方:
s.erase(k);
\(erase(k)\)函數在\(set\)容器中表示刪除集合中元素\(k\)。但在\(multiset\)容器中表示刪除所有等於\(k\)的元素。
時間復雜度變成了\(O(tot+logn)\),其中\(tot\)表示要刪除的元素的個數。
那么,會存在一種情況,我只想刪除這些元素中的一個元素,怎么辦呢?
可以妙用一下:
if((it=s.find(a))!=s.end())
s.erase(it);
\(if\)中的條件語句表示定義了一個指向一個\(a\)元素迭代器,如果這個迭代器不等於\(s.end()\),就說明這個元素的確存在,就可以直接刪除這個迭代器指向的元素了。
s.count(k);
\(count(k)\)函數返回集合中元素\(k\)的個數。\(set\)容器中並不存在這種操作。這是\(multiset\)獨有的。
C++ STL bitset 容器詳解
本篇隨筆講解\(C++STL\)中\(bitset\)容器的用法及常見使用技巧。
\(bitset\)容器概論
\(bitset\)容器其實就是個\(01\)串。可以被看作是一個\(bool\)數組。它比\(bool\)數組更優秀的優點是:節約空間,節約時間,支持基本的位運算。在\(bitset\)容器中,\(8\)位占一個字節,相比於\(bool\)數組\(4\)位一個字節的空間利用率要高很多。同時,\(n\)位的\(bitset\)在執行一次位運算的復雜度可以被看作是\(n/32\),這都是\(bool\)數組所沒有的優秀性質。
\(bitset\)容器包含在\(C++\)自帶的\(bitset\)庫中。
#include<bitset>
\(bitset\)容器的聲明
因為\(bitset\)容器就是裝\(01\)串的,所以不用在< >中裝數據類型,這和一般的\(STL\)容器不太一樣。< >中裝\(01\)串的位數。
如:(聲明一個\(10^5\)位的\(bitset\))
bitset<100000> s;
對\(bitset\)容器的一些操作
1、常用的操作函數
和其他的\(STL\)容器一樣,對\(bitset\)的很多操作也是由自帶函數來實現的。下面,我們來介紹一下\(bitset\)的一些常用函數及其使用方法。
- \(count()\)函數
\(count\),數數的意思。它的作用是數出\(1\)的個數。即\(s.count()\)返回\(s\)中有多少個\(1\).
s.count();
- \(any()/none()\)函數
\(any\),任何的意思。\(none\),啥也沒有的意思。這兩個函數是在檢查\(bitset\)容器中全\(0\)的情況。
如果,\(bitset\)中全都為\(0\),那么\(s.any()\)返回\(false\),\(s.none()\)返回\(true\)。
反之,假如\(bitset\)中至少有一個\(1\),即哪怕有一個\(1\),那么\(s.any()\)返回\(true\),\(s.none()\)返回\(false\).
s.any();
s.none();
- \(set()\)函數
\(set()\)函數的作用是把\(bitset\)全部置為\(1\).
特別地,\(set()\)函數里面可以傳參數。\(set(u,v)\)的意思是把\(bitset\)中的第\(u\)位變成\(v,v\in 0/1\)。
s.set();
s.set(u,v);
- \(reset()\)函數
與\(set()\)函數相對地,\(reset()\)函數將\(bitset\)的所有位置為\(0\)。而\(reset()\)函數只傳一個參數,表示把這一位改成\(0\)。
s.reset();
s.reset(k);
- \(flip()\)函數
\(flip()\)函數與前兩個函數不同,它的作用是將整個\(bitset\)容器按位取反。同上,其傳進的參數表示把其中一位取反。
s.flip();
s.flip(k);
2、位運算操作在\(bitset\)中的實現
\(bitset\)的作用就是幫助我們方便地實現位運算的相關操作。它當然支持位運算的一些操作內容。我們在編寫程序的時候對數進行的二進制運算均可以用在\(bitset\)函數上。
比如:
~:按位取反
&:按位與
|:按位或
^:按位異或
<< >>:左/右移
==/!=:比較兩個\(bitset\)是否相等。
關於位運算的相關知識,不懂的小伙伴請戳這里:
另外,\(bitset\)容器還支持直接取值和直接賦值的操作:具體操作方式如下:
s[3]=1;
s[5]=0;
這里要注意:在\(bitset\)容器中,最低位為\(0\)。這與我們的數組實現仍然有區別。
\(bitset\)容器的實際應用
\(bitset\)可以高效率地對\(01\)串,\(01\)矩陣等等只含\(0/1\)的題目進行處理。其中支持的許多操作對我們處理數據非常有幫助。如果碰到一道\(0/1\)題,使用\(bitset\)或許是不錯的選擇。
詳解C++ STL map 容器
本篇隨筆簡單講解一下\(C++STL\)中的\(map\)容器的使用方法和使用技巧。
map容器的概念
\(map\)的英語釋義是“地圖”,但\(map\)容器可和地圖沒什么關系。\(map\)是“映射容器”,其存儲的兩個變量構成了一個鍵值到元素的映射關系。
比如下圖:
我們可以根據鍵值快速地找到這個映射出的數據。
\(map\)容器的內部實現是一棵紅黑樹(平衡樹的一種),因為比較復雜而且與理解並無多大關系,所以不予介紹,有興趣的讀者可以自己查閱相關的資料。
map容器的聲明
\(map\)容器存在於\(STL\)模板庫#include<map>
中。使用的時候需要先開這個庫。
比如:
#include<map>
map<int,char> mp;
這就建立了一個從一個整型變量到一個字符型變量的映射。
map容器的用法
因為\(map\)容器和\(set\)容器都是使用紅黑樹作為內部結構實現的。所以其用法比較相似。但由於二者用途大有不同,所以其用途還有微妙的差別。對於初學者來講,其更容易涉及到的應該是\(vector\)容器、\(queue\)容器等,但是對於大佬們,經常用個\(set\)、\(map\),沒事再用\(bitset\)壓一壓狀態這都是家常便飯。
如果有想學習\(set,bitset\)容器的,請參考下面兩篇博客,講的比較詳細:
其實,\(C++STL\)容器的使用方式都是差不多的。我們完全可以舉一反三地去類比。與\(bitset\)重定義了許多奇形怪狀新的函數之外,其他都是大致相同的。所以筆者在此不再做幼稚的介紹,大家都是競賽狗,應該都能自己看明白。
常規操作
如其他\(C++STL\)容器一樣,\(map\)支持基本相同的基本操作:
比如清空操作,函數\(clear()\),返回容器大小\(size()\),返回首尾迭代器\(begin(),end()\)等。
插入操作
\(map\)容器的插入操作大約有兩種方法,第一種是類似於數組類型,可以把鍵值作為數組下標對\(map\)進行直接賦值:
mp[1]='a';
當然,也可以使用\(insert()\)函數進行插入:
mp.insert(map<int,char>::value_type(5,'d'));
刪除操作
可以直接用\(erase()\)函數進行刪除,如:
mp.erase('b');
遍歷操作
和其他容器差不多,\(map\)也是使用迭代器實現遍歷的。如果我們要在遍歷的時候查詢鍵值(即前面的那個),可以用it->first
來查詢,那么,當然也可以用it->second
查詢對應值(后面那個)
查找操作
查找操作類比\(set\)的查找操作。但是\(map\)中查找的都是鍵值。
比如:
mp.find(1);
即查找鍵值為\(1\)的元素。
map和pair的關系
我們發現,\(map\)和\(C++\)內置二元組\(pair\)特別相似。那是不是\(map\)就是\(pair\)呢?(當然不是)
那么\(map\)和\(pair\)又有什么關系呢?
@JZYShruraK大佬
首先,\(map\)構建的關系是映射,也就是說,如果我們想查詢一個鍵值,那么只會返回唯一的一個對應值。但是如果使用\(pair\)的話,不僅不支持\(O(log)\)級別的查找,也不支持知一求一,因為\(pair\)的第一維可以有很多一樣的,也就是說,可能會造成一個鍵值對應\(n\)多個對應值的情況。這顯然不符合映射的概念。