關聯式容器及鍵值對
STL中,像vector、list、deque、forward_list(C++11)等,這些容器統稱爲序列式容器,因爲其底層爲線性序列的數據結構,裏面存儲的是元素本身。
STL中,關聯式容器也是用來存儲數據的,與序列式容器不同的是,其裏面存儲的是<key, value>結構的鍵值對,在數據檢索時比序列式容器效率更高。
鍵值對是用來表示具有一 一對應關係的一種結構,該結構中一般只包含兩個成員變量key和value,key代表鍵值,value表示與key對應的信息。就比如:現在要建立一個英漢互譯的字典,那該字典中必然有英文單詞與其對應的中文含義,而且,英文單詞與其中文含義是一 一對應的關係,即通過該應該單詞,在詞典中就可以找到與其對應的中文含義。
SGI-STL中關於鍵值對的定義:
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair()
: first(T1())
, second(T2())
{}
pair(const T1& a, const T2& b)
: first(a)
, second(b)
{}
};
根據應用場景的不桶,STL總共實現了兩種不同結構的管理式容器:樹型結構與哈希結構。
樹型結構的關聯式容器主要有四種:map、set、multimap、multiset。
這四種容器的共同點是:使用平衡搜索樹(即紅黑樹)作爲其底層結果,容器中的元素是一個有序的序列。
此篇文章先來看看set以及multiset這兩個容器。
set
基本概念-->
- set是按照一定次序存儲元素的容器
- 在set中,元素的value也標識它(value就是key,類型爲T),並且每個value必須是唯一的。set中的元素不能在容器中修改(元素總是const),但是可以從容器中插入或刪除它們。
- 在內部,set中的元素總是按照其內部比較對象(類型比較)所指示的特定嚴格弱排序準則進行排序。
- set容器通過key訪問單個元素的速度通常比unordered_set容器慢,但它們允許根據順序對子集進行直接迭代。
- set在底層是用二叉搜索樹(紅黑樹)實現的。
插入 --->
set<int> s;
s.insert(4);
s.insert(5);
s.insert(5);
s.insert(1);
s.insert(3);
s.insert(5);
s.insert(2);
s.insert(5);
s.insert(6);
set<int>::iterator it = s.begin();//迭代器遍歷
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
插入如上數據之後,打印出來的值爲1 2 3 4 5 6。set容器自動對以上數據進行了排序,並且實現了去重。但是不能對set裏的值進行修改。
查找 --->
set<int>::iterator pos = s.find(3);// --- 時間複雜度:O(log(N)),底層是搜索樹,建議使用
//pos = find(s.begin(), s.end(), 3);// --- 時間複雜度:O(N),需要遍歷一遍不建議使用
if (pos != s.end())
{
cout << "找到啦!" << endl;
}
刪除 --->
s.erase(it); //該語句在循環外時,有3就刪除,沒有3就報錯
s.erase(3); //有3就刪除,沒有3就不刪
for (auto e : s)
{
cout << e <<" ";
}
cout << endl;
採用s.erase(3);
這種操作如果沒有3並不會報錯,如果有3則會刪除這個結點。
找到pos位置,採用s.erase(pos);
這種操作如果沒有3則會報錯,如果有3則會刪除這個結點。
交換 --->
set<int> s2;
s2.insert(6);
s2.insert(9);
s2.insert(8);
s2.insert(7);
s2.insert(10);
s2.swap(s);//交換根節點的指針,效率高
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
兩個set的交換的其實是交換結點的指針,效率高。
清空 --->
s.clear();//清掉所有數據
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
遍歷方法 --->
//迭代器遍歷
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
//新式for循環
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
特點--->
- 與map/multimap不同,map/multimap中存儲的是真正的鍵值對<key, value>,set中只放value,但在底層實際存放的是由<value, value>構成的鍵值對。
- set中插入元素時,只需要插入value即可,不需要構造鍵值對。
- set中的元素不可以重複(因此可以使用set進行去重)。
- 使用set的迭代器遍歷set中的元素,可以得到有序序列
- set中的元素默認按照小於來比較
- set中查找某個元素,時間複雜度爲:$ log_2 n $
- set中的元素不允許修改
- set中的底層使用二叉搜索樹(紅黑樹)來實現
multiset
基本概念 --->
- multiset是按照特定順序存儲元素的容器,其中元素是可以重複的。
- 在multiset中,元素的value也會識別它(因爲multiset中本身存儲的就是<value, value>組成的鍵值對,因此value本身就是key,key就是value,類型爲T). multiset元素的值不能在容器中進行修改(因爲元素總是const的),但可以從容器中插入或刪除。
- 在內部,multiset中的元素總是按照其內部比較規則(類型比較)所指示的特定嚴格弱排序準則進行排序。
- multiset容器通過key訪問單個元素的速度通常比unordered_multiset容器慢,但當使用迭代器遍歷時會得到一個有序序列。
- multiset底層結構爲二叉搜索樹(紅黑樹)。
運用 --->
multiset<int> ms;
ms.insert(3);
ms.insert(4);
ms.insert(5);
ms.insert(2);
ms.insert(1);
ms.insert(5);
ms.insert(4);
ms.insert(3);
//可以插入重複的相同的key值
for (auto e : ms)
{
cout << e << " ";
}
cout << endl;
auto mit = ms.find(3);
if (mit != ms.end())//因爲有多個3,所以找到的是中序遍歷的第一個
{
cout << "找到啦~!" << endl;
while (*mit == 3)//繼續遍歷輸出所有的3
{
cout << *mit <<" ";
++mit;
}
cout << endl;
}
multiset的接口和set基本都相同,但是值得注意的是multiset可以插入key相同的值。
multiset允許key的冗餘,如果用find查找key值時,找到的是中序遍歷第一個,因此不斷遍歷下午可以找到這個multiset裏所有的key值。
multiset和set一樣不能夠對數據進行修改。
特點 --->
- multiset中再底層中存儲的是<value, value>的鍵值對
- mtltiset的插入接口中只需要插入即可
- 與set的區別是,multiset中的元素可以重複,set是中value是唯一的
- 使用迭代器對multiset中的元素進行遍歷,可以得到有序的序列
- multiset中的元素不能修改
- 在multiset中找某個元素,時間複雜度:$ O(log_2 N) $
- multiset的作用:可以對元素進行排序