1. 程式人生 > >std::set與std::multiset使用總結

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

,由C++標準庫提供。
注意——
  所謂的“排序準則”,必須定義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元素能夠區分lessequal的關係,即<和=的關係。而區分兩者的關係則是為了辨別元素是否重複——如果兩個元素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是以節點為基礎的容器,如果節點構建失敗,容器保持原樣。此外,由於(元素)解構函式通常不丟擲異常,因此移除元素不能失敗。
  對於單一元素的插入操作,能夠保證其“要麼成功,要麼沒有任何作用”的操作原則,但是對於多重元素的插入操作,必須保證其排序準則不丟擲異常,才能保證其達到這一點。