《C++ Primer》讀書筆記第十一章-2-關聯容器操作
筆記會持續更新,有錯誤的地方歡迎指正,謝謝!
關聯容器操作
這部分的內容較多,但是順序容器那部分掌握了,這裡會很快,一通百通嘛。
map的節點是一對資料,set的節點是一個數據。
關聯容器迭代器:map的value_type是pair<const key_type, mapped_type>
,所以map迭代器只能改變關鍵字對映的值(mapped_type),不能修改關鍵字;set的value_type等於key_type,都是const關鍵字,不能修改。
- map使用鍵值對的方式來儲存資料,鍵不能有重複的,值可以重複,map使用鍵來儲存資料,系統會根據鍵來自動將資料排序;set鍵不能有重複,使用鍵來儲存資料,系統會根據該值來自動將資料排序。
- C++STL中map,set的底層實現全是用的紅黑樹,而且關鍵字都不能重複,而multimap和multiset關鍵字可以重複。(紅黑樹:實際上是AVL的一種變形,但是其比AVL(平衡二叉搜尋樹)具有更高的插入效率,當然查詢效率會平衡二叉樹稍微低一點點,畢竟平衡二叉樹太完美了。但是這種查詢效率的損失是非常值得的。它的操作有著良好的最壞情況執行時間,並且在實踐中是高效的: 它可以在O(log n)時間內做查詢,插入和刪除,這裡的n是樹中元素的數目。)
- 另外,比如:map採用紅黑樹實現,比hash_map(未進入stl)穩定,但是一般沒有hash_map快。
- 資料結構的的穩定與非穩定的是根據排序移動後原來在前面的邏輯位置仍然在前面保持不變的,所以map和set是穩定排序,multimap和multiset是不穩定排序。
接下來就要介紹各種操作了:
關聯容器迭代器
map<string, size_t> word_count; //單詞計數程式
auto map_it = word_count.begin(); //map_it作為迭代器指向首元素
cout << map_it->first; //列印鍵
map_it->first = "new"; //錯誤:關鍵字是const
//關鍵字就是鍵,鍵的型別就是<>中的某個型別。
set的迭代器是const:
map和set是關聯容器,類似於集合,裡面的元素不會重複,而且呈現為有序性。
set<int > iset = {0, 1, 2, 3};
set<int>::iterator set_it = iset.begin();
if(set_it != iset.end())
{
*set_it = 24; //錯誤:鍵是const
}
遍歷關聯容器:
auto map_it = word_count.cbegin();
while(map_it != word_count.cend())
{
cout << map_it->first << " " << map_it->second << endl;
++map_it;
}
我們通常不對關聯容器使用泛型演算法,因為鍵是const這一特性就限制了很多泛型演算法,所以要用演算法也只能用那些只讀的演算法!
新增元素
關聯容器的inset成員向容器中新增一個範圍內的元素或n個元素。由於map和set包含不重複的關鍵字,因此插入一個已存在的元素對容器沒任何影響。而multimap和multiset允許插入已存在的元素。
vector<int> ivec = {0, 1, 0, 1};
set<int> set2;
set2.insert(ivec.cbegin(), ivec.cend()); //第一種新增元素方式set2 = {0, 1}
set2.insert( {1 ,2 ,3} ); //第二種新增元素方式set2={0, 1, 2, 3}
set2.emplace(2); //不能插入2,因為2已存在
補充:
set容器中emplace和insert都往 set 裡增加一個元素。
向map新增元素:
對一個map進行insert操作時,必須記住元素型別是pair(pair中包含兩個資料值,兩個資料的型別可以不同)。
pair是一種模板型別,其中包含兩個資料值,兩個資料的型別可以不同。
通常,對於想要插入的資料,並沒有一個現成的pair物件,也就是說只能在insert的引數列表中建立一個pair。
map<string, size_t> word_count;
//向word_count插入word的四種方法
word_count.insert( {word, 1} );//在引數列表中使用花括號初始化建立了一個pair。
word_count.insert( make_pair(word, 1) );//也可在引數列表中呼叫make_pair構造pair。
word_count.insert( pair<string, size_t>(word, 1) );//也可顯示構造pair。
word_count.insert( map<string, size_t>::value_type(word, 1) );先構造一個pair型別,再構造該型別的一個新物件(物件型別為pair)。
檢測insert的返回值:
只有當關鍵字不存在時才插入,關聯容器的insert()和emplace()返回一個pair。pair的first成員是一個迭代器,指向具有給定關鍵字的元素;second成員是一個bool值,告訴我們插入成功(返回true)還是插入失敗(返回false)。
向multiset或multimap新增元素:
一個作者可能有多個作品,所以,我們應該用multimap,關鍵字不必唯一,呼叫insert總會插入一個元素:
multimap<string, string> authors;
authors.insert( {"金庸", "飛狐外傳"} );
authors.insert( {"金庸", "雪山飛狐"} );
刪除元素
元素刪除:可以高效的刪除,(和插入一樣)會自動調整使紅黑樹平衡。
關聯容器erase:若傳入迭代器,必須保證指向真實元素;若傳入關鍵字,則返回被刪去的所有該關鍵字的元素的數量。
set<int> s;
s.erase(2);//刪除鍵為2的元素
map的下標操作
map下標操作(只能用於map(有序)和unorders_map(無序)):返回mapped_type(關鍵字對映的值),解引用返回value_type(即pair<const key_type, mapped_type>
);若關鍵字還不存在,會建立一個對應關鍵字且值為0的新元素,已存在則返回最近一次賦的值。
只有map有下標,set型別沒有是因為人家就一個鍵,沒有值,而multimap沒有是因為可能有多個值跟同一個鍵相關。
舉例:
map<string, size_t> word_count;
word_count["Anna"] = 1;
這樣等價於把{“Anna”, 1}插入了。 由於下標運算子可能插入新元素,所以我們只能對非const的map使用下標操作。
訪問元素
C++提供了很多訪問元素的方法:
set<int> iset = {0, 1, 2, 3, 4};
iset.find(1); //返回一個指向1的迭代器
iset.find(-1); //返回一個指向iset.end()的迭代器
iset.count(1); //(返回該元素的數量),此處返回1
iset.count(11); //返回0
iset.lower_bound(2); //返回一個迭代器指向第一個不小於2的元素,就是2
iset.upper_bound(2); //返回一個指向第一個大於2的元素迭代器,3
無序容器
無序容器對鍵型別的要求:
我們知道它是使用==來比較元素的,那怎麼比較元素呢?它並不是直接比較的,要使用一個hash型別的物件為鍵生成一個雜湊值,再去判等。標準庫 已經為內建型別(包括指標)都實現了hash模板,還有string和以後要介紹的智慧指標也定義了hash,這就是說,我們可以用無序容器直接去裝這些型別的元素。
但是,如果要裝自定義型別的元素,我們得提供自己的hash模板(以後再介紹怎麼做到這一點)或者用另一種方法,類似於有序容器過載 關鍵字型別的預設比較操作。
以前講了那麼多都是有序的關聯容器,接下來我們來介紹下無序的關聯容器。在關鍵字型別的元素沒有明顯的序關係的情況下,無序容器很好用。
例子:
我們來用無序容器unordered_map重寫最初的單詞計數程式:
unordered_map<string, size_t> word_count;
string word;
while(cin >> word)
{
++word_count[word];
}
對於每個單詞,我們還是得到相同的計數結果,但是不太可能按字典序輸出了。
無序容器是雜湊表實現的,無序容器查詢的時間複雜度可達到O(常數),記憶體消耗在於雜湊表;而有序容器是紅黑樹實現的,查詢的時間複雜度為log(n),但記憶體佔用通常會少點。