1. 程式人生 > >【C++ STL學習之五】容器set和multiset

【C++ STL學習之五】容器set和multiset

一、set和multiset基礎

set和multiset會根據特定的排序準則,自動將元素進行排序。不同的是後者允許元素重複而前者不允許。


需要包含標頭檔案:

#include <set>

set和multiset都是定義在std空間裡的類模板:

template<class _Kty,
    class _Pr = less<_Kty>,
    class _Alloc = allocator<_Kty> >
class set
template<class _Kty,
	class _Pr = less<_Kty>,
	class _Alloc = allocator<_Kty> >
class multiset

只要是可復賦值、可拷貝、可以根據某個排序準則進行比較的型別都可以成為它們的元素。第二個引數用來定義排序準則。預設準則less是一個仿函式,以operator<對元素進行比較。

所謂排序準則,必須定義strict weak ordering,其意義如下:

1、必須使反對稱的。

對operator<而言,如果x<y為真,則y<x為假。

2、必須使可傳遞的。

對operator<而言,如果x<y為真,且y<z為真,則x<z為真。

3、必須是非自反的。

對operator<而言,x<x永遠為假。

因為上面的這些特性,排序準則可以用於相等性檢驗,就是說,如果兩個元素都不小於對方,則它們相等。

二、set和multiset的功能

和所有關聯式容器類似,通常使用平衡二叉樹完成。事實上,set和multiset通常以紅黑樹實作而成。

自動排序的優點是使得搜尋元素時具有良好的效能,具有對數時間複雜度。但是造成的一個缺點就是:

不能直接改變元素值。因為這樣會打亂原有的順序。

改變元素值的方法是:先刪除舊元素,再插入新元素。

存取元素只能通過迭代器,從迭代器的角度看,元素值是常數。

三、操作函式

建構函式和解構函式

set的形式可以是:


有兩種方式可以定義排序準則:

1、以template引數定義:

set<int,greater<int>> col1;
此時,排序準則就是型別的一部分。型別系統確保只有排序準則相同的容器才能被合併。

程式例項:

#include <iostream>
#include <set>
using namespace std;

int main()
{
	set<int> s1;
	set<int,greater<int> > s2;

	for (int i = 1;i < 6;++i)
	{
		s1.insert(i);
		s2.insert(i);
	}
	if(s1 == s2)
		cout << "c1 equals c2 !" << endl;
	else
		cout << "c1 not equals c2 !" << endl;
}
程式執行會報錯。但是如果把s1的排序準則也指定為greater<int>便執行成功。

2、以建構函式引數定義。

這種情況下,同一個型別可以運用不同的排序準則,而排序準則的初始值或狀態也可以不同。如果執行期才獲得排序準則,而且需要用到不同的排序準則,這種方式可以派上用場。

程式例項:

#include <iostream>
#include "print.hpp"
#include <set>
using namespace std;

template <class T>
class RuntimeCmp{
public:
	enum cmp_mode{normal,reverse};
private:
	cmp_mode mode;
public:
	RuntimeCmp(cmp_mode m = normal):mode(m){}

	bool operator()(const T &t1,const T &t2)
	{
		return mode == normal ? t1 < t2 : t2 < t1;
	}

	bool operator==(const RuntimeCmp &rc)
	{
		return mode == rc.mode;
	}
};

typedef set<int,RuntimeCmp<int> > IntSet;

void fill(IntSet& set);

int main()
{
	IntSet set1;
	fill(set1);
	PRINT_ELEMENTS(set1,"set1:");

	RuntimeCmp<int> reverse_order(RuntimeCmp<int>::reverse);

	IntSet set2(reverse_order);
	fill(set2);
	PRINT_ELEMENTS(set2,"set2:");

	set1 = set2;//assignment:OK
	set1.insert(3);
	PRINT_ELEMENTS(set1,"set1:");

	if(set1.value_comp() == set2.value_comp())//value_comp Returns the comparison object associated with the container
		cout << "set1 and set2 have the same sorting criterion" << endl;
	else
		cout << "set1 and set2 have the different sorting criterion" << endl;
}

void fill(IntSet &set)
{
	set.insert(4);
	set.insert(7);
	set.insert(5);
	set.insert(1);
	set.insert(6);
	set.insert(2);
	set.insert(5);
}
執行結果:
雖然set1和set2的而比較準則本身不同,但是型別相同,所以可以進行賦值操作。

非變動性操作

注意:元素比較操作只能用於型別相同的容器。

特殊的搜尋函式

賦值

賦值操作兩端的容器必須具有相同的型別,但是比較準則本身可以不同,但是其型別必須相同。如果比較準則的不同,準則本身也會被賦值或交換。


迭代器相關函式


元素的插入和刪除

注意:插入函式的返回值不完全相同。

set提供的插入函式:

pair<iterator,bool> insert(const value_type& elem); 
iterator  insert(iterator pos_hint, const value_type& elem); 
multiset提供的插入函式:
iterator  insert(const value_type& elem); 
iterator  insert(iterator pos_hint, const value_type& elem);
返回值型別不同的原因是set不允許元素重複,而multiset允許。當插入的元素在set中已經包含有同樣值的元素時,插入就會失敗。所以set的返回值型別是由pair組織起來的兩個值:

第一個元素返回新元素的位置,或返回現存的同值元素的位置。第二個元素表示插入是否成功。

set的第二個insert函式,如果插入失敗,就只返回重複元素的位置!

但是,所有擁有位置提示引數的插入函式的返回值型別是相同的。這樣就確保了至少有了一個通用型的插入函式,在各種容器中有共通介面。

注意:還有一個返回值不同的情況是:作用於序列式容器和關聯式容器的erase()函式:

序列式容器的erase()函式:

 iterator erase(iterator pos); 
 iterator erase(iterator beg, iterator end);
關聯式容器的erase()函式:
 void     erase(iterator pos); 
 void     erase(iterator beg, iterator end); 
這完全是為了效能的考慮。因為關聯式容器都是由二叉樹實現,搜尋某元素並返回後繼元素可能很費時。

五、set應用示例:
#include <iostream>
#include <set>
using namespace std;

int main()
{
	typedef set<int,greater<int> > IntSet;
	IntSet s1;

	s1.insert(4);
	s1.insert(3);
	s1.insert(5);
	s1.insert(1);
	s1.insert(6);
	s1.insert(2);
	s1.insert(5);
	//the inserted element that has the same value with a element existed is emitted

	copy(s1.begin(),s1.end(),ostream_iterator<int>(cout," "));
	cout << endl << endl;

	pair<IntSet::iterator,bool> status = s1.insert(4);
	if(status.second)
		cout << "4 is inserted as element "
		<< distance(s1.begin(),status.first) + 1 << endl;
	else
		cout << "4 already exists in s1" << endl;
	copy(s1.begin(),s1.end(),ostream_iterator<int>(cout," "));
	cout << endl << endl;

	set<int>  s2(s1.begin(),s1.end());//default sort criterion is less<
	copy(s2.begin(),s2.end(),ostream_iterator<int>(cout," "));
	cout << endl << endl;
}

上述程式最後新產生一個set:s2,預設排序準則是less。以s1的元素作為初值。

注意:s1和s2有不同的排序準則,所以他們的型別不同,不能直接進行相互賦值或比較。

執行結果: