1. 程式人生 > >C++之set和multiset紅黑樹

C++之set和multiset紅黑樹

set和multiset 會根據特定的排序準則,自動將元素排序。兩者不同之處,在於multiset允許元素重複,而set不允許重複,如圖1所示。

圖1 set和multiset

在使用set和multiset之前,需要包含頭標頭檔案<set>,Set 和multiset的原型如下:

namespace std
{
	template < class T,
			  class Compare = less<T>,
			  class Allocator = allcator<t> >
	class set;
	
	template <class T,
			  class Compare = less<T>,
			  class Allocator = allcator<t> >
	class multiset;	
}

我們需要著重關注的是第二個模板引數,如果我們沒有傳入特殊的排序準則,就採用預設的less排序方式。Less是仿函式,採用operator<的比較方式。

Set 和multiset資料結構

和所有標準關聯模式類似,set和multiset內部資料結構通常是以紅黑樹(RBT,平衡二叉樹)構成,紅黑樹在改變元素和搜尋元素具有良好的效能。比如在搜尋元素時,set的搜尋成員函式演算法具有對數複雜度,速度相對較快。其內部結構如圖2。

    圖2 內部資料結構

Set的排序準則

       Set的預設排序準則是less函式,我們也可以定義其他的排序準則,有以下兩種排序定義方式:

1.template引數定義之:

       std::set<int,std::greate<int> > coll;

       這種情況下,排序準則是是型別的一部分,這是排序準則的通常指定用法,如果排序準則不相同,則不能相互比較。

2.以建構函式引數定義之:

       這種情況下,同一個型別可以運用不同的排序準則,而排序準則的初始值或狀態可以不同。如果執行期間才獲得排序準則,而且需要用到不同的排序準則,以建構函式引數定義的方式則派上用場了。程式碼實現見例項三。

Set和multiset操作函式

       和vector、deque、list相比,set主要不同之處在於插入元素時具有自動排序功能的,其餘的操作函式基本都相同,此次只列舉set的特殊函式,程式碼實現見例項一。

set和multiset的搜尋操作函式
count(elem)返回“元素值為elem”的元素個數
find(elem)返回“元素值為elem”的第一個元素,如果找不到就返回end()
lower_bound(elem)返回elem的第一個可安插位置,也就是“元素值>=elem”的第一個位置
upper_bound(elem))返回elem的最後一個可安插位置,也就是“元素值>elem”的第一個位置
equal_range(elem)返回elem可安插的第一個位置和最後一個位置,即equal_range()將lower_bound()和upper_bound()的返回值做個一個pair返回。

Set的intsert返回值:

pair<iterator, bool>  insert(const vaule_type& elem);

iterator                     insert(iterator pos_hint, const value_type& elem);//引數pos_hint示位置提示

multiset的intsert返回值:

iterator                    insert(const vaule_type& elem);

iterator                    insert(iterator pos_hint, const value_type& elem);//;//引數pos_hint示位置提示

由於Multiset允許元素重複,而set不允許重複。如果將某元素插入一個set中,如果set中已經包含同值元素,則安插操作將失敗,所以set的返回值型別可以是pari結構,返回值含義如下:

1.pair結構中的second成員表示安插是否成功。

2.pair結構中的first成員返回新元素的位置,或現存同值元素的位置。

set<double> c;
...
if(c.insert(3.3).second)
{
	std::cout << "3.3 inserted" << endl;
}
else
{
	std::cout << "3.3 already exists << endl";
}

帶有位置提示引數的安插函式,其返回值型別都一樣,不論是set還是multiset。位置提示引數僅是可能給容器插入帶來效能的提升,並不是指定插入的位置。

帶有位置提示引數的安插函式是各種容器中的通用介面,事實上通用型安插迭代器(general inserters)就是通過這個介面實現的。

示例一

set或multiset的搜尋函式舉例

void SetExample1()
{
	cout << "*********SetExample1*********" << endl;

	set<int> c;
	c.insert(1);
	c.insert(2);
	c.insert(4);
	c.insert(5);
	c.insert(6);

	cout << "lower_bound(3): " << *c.lower_bound(3) << endl;
	cout << "upper_bound(3): " << *c.upper_bound(3) << endl;
	cout << "equal_bound(3): " << *c.equal_range(3).first 
		<< " " << *c.equal_range(3).second << endl;

	cout << "lower_bound(5): " << *c.lower_bound(5) << endl;
	cout << "upper_bound(5): " << *c.upper_bound(5) << endl;
	cout << "equal_bound(5): " << *c.equal_range(5).first 
		<< " " << *c.equal_range(5).second << endl;
}

示例二

set的綜合應用

void SetExample2()
{
	cout << "*********SetExample2*********" << endl;

	typedef set<int, greater<int> >IntSet;
	IntSet IntSetContainer;
	IntSetContainer.insert(4);
	IntSetContainer.insert(3);
	IntSetContainer.insert(5);
	IntSetContainer.insert(1);
	IntSetContainer.insert(6);
	IntSetContainer.insert(2);
	IntSetContainer.insert(5);

	IntSet::iterator pos;
	for (pos = IntSetContainer.begin(); pos != IntSetContainer.end(); ++pos)
	{
		cout << *pos << " ";
	}
	cout << endl;

	pair<IntSet::iterator, bool> status = IntSetContainer.insert(4);
	if (status.second)
	{
		cout << "4 insertend as element " << distance(IntSetContainer.begin(), status.first) + 1 <<endl;
	} 
	else
	{
		cout << "4 already exists!" << endl;
	}
	
	//assign element to another set  with ascending order
	set<int> nSetContainLess(IntSetContainer.begin(), IntSetContainer.end());

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

	//remove all elements from begin pos to element with value 3
	nSetContainLess.erase(nSetContainLess.begin(), nSetContainLess.find(3));
	
	//remove all elements with value 5;
	int num;
	num = nSetContainLess.erase(5);
	cout << num << "element(s) removed" << endl;
	
	//print all elemts
	copy(nSetContainLess.begin(), nSetContainLess.end(), ostream_iterator<int>(cout, " "));
	cout << endl;
}

示例三

執行期指定排序準則

實現原理:

       根據排序準則insert函式會在某個位置插入新元素,新插入的新元素會保證樹的平衡狀態不被改變,具體的排序準則均是通過運算子過載實現的。比如less仿函式呼叫operator(),透過"<"實現,great仿函式呼叫operator(),透過">"。執行期實現指定排序準則則是根據不同的引數呼叫不同操作符號來實現

template<class _Ty>
struct greater: public binary_function<_Ty, _Ty, bool>
{	
	// functor for operator>
	bool operator()(const _Ty& _Left, const _Ty& _Right) const
	{	// apply operator> to operands
		return (_Left > _Right);
	}
};

template<class _Ty>
struct less: public binary_function<_Ty, _Ty, bool>
{	
	// functor for operator<
	bool operator()(const _Ty& _Left, const _Ty& _Right) const
	{	
		// apply operator< to operands
		return (_Left < _Right);
	}
};

實現原始碼

template<class T>
class RuntimeCmp
{
public:
	enum cmp_mode {normal, reverse};
private:
	cmp_mode emMode;

public:
	RuntimeCmp(cmp_mode m = normal) : emMode(m){
		if (m== normal)
		{
			cout << "normal" << endl;
		}
		else
		{

			cout << "reverse" << endl;
		}
	}
	bool operator() (const T &t1, const T &t2)const
	{
		return emMode == normal ? t1 < t2 :t2 < t1;//根據模式值確定需要呼叫的值
	}
	bool operator== (const RuntimeCmp& rc)
	{
		return emMode == rc.emMode;
	}
};

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

void fill(IntSet & nSet)
{
	nSet.insert(4);
	nSet.insert(7);
	nSet.insert(5);
	nSet.insert(1);
	nSet.insert(6);
	nSet.insert(2);
	nSet.insert(5);
}
void SetExample3()
{
	cout << "*********SetExample3*********" << endl;

	IntSet nSetContainer;
	fill(nSetContainer);
	IntSet::iterator it = nSetContainer.begin();

	PrintElements(nSetContainer, "Set1: ");

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

	IntSet nSetContainer2(reverse_order);
	fill(nSetContainer2);
	PrintElements(nSetContainer2, "Set2: ");

	nSetContainer = nSetContainer2;//不見賦值了元素,也賦值了排序準則
	nSetContainer.insert(3);
	PrintElements(nSetContainer, "Set1: ");

	if (nSetContainer.value_comp() == nSetContainer2.value_comp())
	{
		cout << "set1 and set2 have same sorting criterion" << endl;
	} 
	else
	{
		cout << "set1 and set2 have different sorting criterion" << endl;
	}

}