std::set與std::multiset使用總結
Set和Multiset
Set和Multiset都會根據特定的排序準則,自動將元素排序,兩者不同在於multiset允許元素重複,而set不允許元素重複。
Set和Multiset的使用均需要包含標頭檔案<set>
#include<set>
在這個標頭檔案中,上述兩個型別都被定義為std名稱空間中的class template:
template<
template Key,
template Compare = std::less<Key>,
template Allocator = std::allocator<Key>
> class set;
template<
template Key,
template Compare = std::less<Key>,
template Allocator = std::allocator<Key>
> class multiset;
必須是能夠比較的物件,才能作為set或者multiset的元素(即所謂的comparable)。排序準則預設為less——這是個函式物件,以operator<
對元素進行比較。第三個引數指定記憶體模型,預設是allocator
注意——
所謂的“排序準則”,必須定義strick weak ordering,其定義如下:
1. 必須是非對稱的(anisymmertric)
對operator<
而言,如果x < y為true,那麼y < x 必為false。
對判別式 op()
而言,如果op(x,y)為ture,那麼op(y,x)必須為false。
2. 必須是可傳遞的(transitive)
即,如果x<y, y<z,那麼x<z。
3. 必須是非自反的(irreflexive)
即,x < x永遠為false
4. 必須具有等效傳遞性(transitivity of equivalence)
即,如果a等於b且b等於c,那麼a必然等於c。
規則1~4的目的是保證set和multiset元素能夠區分less
和equal
的關係,即<和=的關係。而區分兩者的關係則是為了辨別元素是否重複——如果兩個元素x和y,x<y為ture且y<x為true,那麼x和y被視為重複元素。
Multiset的想等元素排序是穩定排序,因此C++11保證插入和刪除的相對位置的穩定性。
Set和Multiset的能力
與所有的關聯式容器類似,set和multiset通常以平衡二叉樹完成——通常是紅黑樹,紅黑樹在改變元素數量和元素搜尋方面具有出色的效能,它保證節點安插最多做兩個重新連結的動作,而到達某一元素的最長路徑的深度,最多是最短路徑深度的兩倍。
set和multiset具有自動排序的功能,自動排序使得平衡二叉樹在查詢元素時具有卓越的效率。但同時也帶來一種限制——set和multiset無法直接改變元素的值,因為這樣會打亂原本的順序。
set和multiset本身並不提供任何操作函式可以直接訪問元素,從迭代器的角度而言,元素的值是常量。因此,要改變元素的值,只能刪除再插入。
Set和Multiset的操作函式
建立、複製和解構函式
set的建構函式和解構函式,如下表所示——
序號 | 操作 | 效果 |
---|---|---|
1 | set c | Default建構函式,產生一個空set,沒有任何元素 |
2 | set c<op> | 建立一個空的set,並以op為排序規則 |
3 | set c(c2) set c=c2 |
Copy建構函式,建立c2同型set併成為c2的一份副本,該複製是深度複製 |
4 | set c(rv) set c=rv |
Move建構函式,rv是一個set右值引用,那麼這裡的建構函式是一個Move建構函式,建立一個新的set,取右值內容(C++11新特性) |
5 | set c(beg,end) | 建立一個set,並以迭代器所指向的區間[beg,end)作為元素值,該建構函式支援從其他容器型別接收元素 |
6 | set c(beg,end,op) | 建立一個set,並以迭代器所指向的區間[beg,end)作為元素值,同時以op作為排序準則 |
7 | set c(initlist) set c=initlist |
建立一個set,以初值列initlist元素為初值(C++11新特性) |
8 | c.~set() | 銷燬所有元素,釋放記憶體 |
其中,上表中set的形式可以置換為以下任意一種——
序號 | set | 效果 |
---|---|---|
1 | set<Elem> | 使用預設排序準則,以Elem作為元素型別的set |
2 | set<Elem,op> | 使用op作為排序準則,以Elem作為元素型別的set |
3 | multiset<Elem> | 使用預設排序規則,以Elem作為元素型別的multiset |
4 | multiset<Elem,op> | 使用op作為排序準則,以Elem作為元素型別的multiset |
#include<set>
#include<iostream>
using namespace std;
int main(){
cout << "multiset:" << endl;
multiset<string> ms{ "B","C","A","A","A" };
for (auto&it : ms) {
cout << it << " ";
}
cout << endl << "set:" << endl;
set<string> s{ "B","C","A","A" };
for (auto&it : s) {
cout << it << " ";
}
return 0;
}
上述程式碼中,使用range-based for迴圈對set/multiset的所有元素進行遍歷,顯然——
1.set/multiset具有自動排序功能;
2.set不允許重複元素,儘管使用初值列初始化有重複元素,但是即不生效,也不報錯。
非更易型操作(Nonmodifying Operating)
Set也提供元素比較、查詢大小等操作——
序號 | 操作 | 效果 |
---|---|---|
1 | c.empty() | 容器為空返回true,不為空返回false,相當於size()==0 |
2 | c.size() | 返回當前元素的個數 |
3 | c.max_size() | 返回元素個數之最大可能量 |
4 | c1==c2 | 對每個元素呼叫c1==c2,全部相等返回true |
5 | c1!=c2 | 只要有一個元素相等,返回true,相當於!(c1==c2) |
6 | c1>c2,c1>=c2,c1<c2,c1<=c2 | 同上,依次類推 |
7 | c.key_comp() | 返回比較準則 |
8 | c.value_comp() | 返回針對value的比較準則 |
set的元素比較只適用於型別相同的元素,這裡的型別相同包括元素型別和排序準則,在set中,排序準則是作為型別看待的,因此不同排序型別的set無法比較,比如——
std::set<float> c1;
std::set<float,std::greater<float>> c2;
if(c1==c2)// 編譯錯誤
{
...
}
特殊的查詢函式
Set和Multiset在元素查詢方面具有優化設計,因此提供了特殊的查詢函式,
序號 | set | 效果 |
---|---|---|
1 | c.count(val) | 返回元素為val的個數 |
2 | c.find(val) | 返回元素為val的第一個位置,注意這裡返回的是一個迭代器 |
3 | c.lower_bound(val) | 返回val第一個可插入的位置,即第一個元素值<=val的位置 |
4 | c.upper_bound(val) | 返回val最後一個可插入的位置,即第一個元素值<val的位置 |
5 | c.equal_range(val) | 返回val可以被插入的第一個位置和最後一個位置的範圍 |
通過一個例子暫時其查詢函式的能力——
multiset<int> ms{ 1,1,1,2,2,2,3,3,4,5,6,7,8 };
cout << "ms.count(1) = " << ms.count(1) << endl;
multiset<int>::iterator it1 = ms.find(2);
cout << "ms.find(2) :" << endl << *it1 << endl;
pair<multiset<int>::iterator,multiset<int>::iterator> it2 = ms.equal_range(3);
cout << "ms.equal_range(3) :" << endl << *it2.first << " " << *it2.second << endl;
其中,除了count是返回int型之外,其餘的查詢函式返回的都是set<typename>::iterator
迭代器,其指向的位置是已構建的平衡二叉樹的位置,而不是初始化時的位置。
賦值(Assignment)
Set只提供任何容器都提供的基本賦值操作:
序號 | 操作 | 效果 |
---|---|---|
1 | c = c2 | 將c2的全部元素賦值給c |
2 | c = rv | 將rv右值語義所有元素以Move assign的方式賦值給c(C++11新特性) |
3 | c = initlist | 將初值列所有元素賦值給c(C++11新特性) |
4 | c1.swap(c2) swap(c1,c2) |
置換c1和c2的資料 |
迭代器函式(Iterator Function)
Set和Multiset不提供元素訪問,所以只能採用range-based for迴圈,迭代器的使用就尤為常見:
序號 | 操作 | 效果 |
---|---|---|
1 | c.begin() | 返回一個bidirectional iterator指向第一個元素 |
2 | c.end() | 返回一個bidirectional iterator指向的之後一個元素 |
3 | c.cbegin() | 返回一個const bidirectional iterator指向的第一個元素(C++11新特性) |
4 | c.cend() | 返回一個const bidirectional iterator指向的最後一個元素(C++11新特性) |
5 | c.rbegin() | 返回一個反向迭代器(reverse iterator)指向的第一個元素 |
6 | c.rend() | 返回一個reverse iterator指向的最後一個元素 |
7 | c.crbegin() | 返回一個const reverse iterator指向的第一個元素(C++新特性) |
8 | c.crend() | 返回一個const reverse iterator指向的最後一個元素(C++11新特性) |
multiset<string> ms{ "apple","xiaomi","huawei","vivo","oppo" };
// 遍歷set常用range-based for迴圈
for (auto&elem : ms) {
cout << elem << " ";
}
cout << endl;
// 或者使用迭代器
for (auto it = ms.cbegin(); it != ms.cend(); it++) {
cout << *it << " ";
}
對Set和Multiset而言,這裡的迭代器只能是雙向迭代器,因此,對於那些只能夠支援“隨機訪問迭代器”的STL演算法(如random shuffling),Set和Multiset無福消受。
更重要的是,從迭代器的角度而言,Set和Multiset的元素都被視為常量,這是因為改變其元素值會打亂既有順序,因此無法通過迭代器改變其元素值,也無法對Set和Multiset採用任何易更型演算法,只能通過其提供的成員函式來改變其元素值。
插入和移除(Inserting and Removing)
Set的所有插入和移除操作如下表所示——。
序號 | 操作 | 效果 |
---|---|---|
1 | c.insert(val) | 插入val元素,並返回新元素的位置,不論是否成功 |
2 | c.insert(pos,val) | 在iterator指向的pos位置的前方插入一個元素val的副本,並返回新元素的位置(pos只是一個提示,該提示恰當會加快查詢過程) |
3 | c.insert(beg,end) | 插入區間[beg,end)內所有元素的副本,無返回值 |
4 | c.insert(initilist) | 插入initilist的所有元素的副本,無返回值(C++11新特性) |
5 | c.emplace(args…) | 插入一個以args為初值的元素,並返回新元素的位置,無論是否成功(C++11新特性) |
6 | c.emplace_hint(pos,args…) | 插入一個以args為初值的元素,並返回新元素的位置,pos是個位置提示,如果恰當,將大大提高插入效率 |
7 | c.erase(val) | 移除與val相等的所有元素,返回被移除的個數 |
8 | c.erase(pos) | 移除迭代器iterator指向的元素,無返回值 |
9 | c.erase(beg,end) | 移除區間[beg,end)內的所有元素,無返回值 |
10 | c.clear() | 移除所有元素,並將容器清空 |
注意,用以插入元素的函式insert和emplace,其返回型別不盡相同,主要原因在於Set不允許元素重複,而Multiset可以——
// Set提供如下介面
pair<iterator,bool> insert(const value_type&val);
iterator insert(const_iterator posHint,const value_type&val);
template<typename args>
pair<iterator,bool> emplace(args);
template<typename args>
iterator emplace_hint(const_iterator posHint,const value_type&val);
// Multiset 提供如下介面
iterator insert(const value_type&val);
iterator insert(const_iterator posHint,const value_type&val);
template<typename args>
iterator emplace(args);
template<typename args>
iterator emplace_hint(const_iterator posHint,const value_type&val);
兩者區別在於,Set不支援重複元素,因此返回的是一個pair組織起來的兩個值,其中pair.second表示是否插入成功,pair.first表示插入元素之後的位置(如果值相同,則返回相同元素的位置)。
同時,C++11保證,Multiset的insert、emplace和erase操作都會保證元素間的相對次序,插入元素都會被放到既有等值元素的末尾。
異常處理
Set和Multiset是以節點為基礎的容器,如果節點構建失敗,容器保持原樣。此外,由於(元素)解構函式通常不丟擲異常,因此移除元素不能失敗。
對於單一元素的插入操作,能夠保證其“要麼成功,要麼沒有任何作用”的操作原則,但是對於多重元素的插入操作,必須保證其排序準則不丟擲異常,才能保證其達到這一點。