1. 程式人生 > >STL系列之六 set與hash set

STL系列之六 set與hash set

STL系列之六 set與hash_set

set和hash_set是STL中比較重要的容器,有必要對其進行深入瞭解。在STL中,set是以紅黑樹(RB-tree)作為底層資料結構的,hash_set是以Hash table(雜湊表)作為底層資料結構的。set可以在時間複雜度為O(logN)情況下插入、刪除和查詢資料。hash_set操作的時間複雜度則比較複雜,這取決於雜湊函式和雜湊表的負載情況。下面列出set和hash_set的常用函式:

 

 

 

 

 

 

 

set和hase_set的更多函式請查閱

MSDN

 

set的使用範例如下(hash_set類似):

// by MoreWindows( http://blog.csdn.net/MoreWindows )
#include <set>
#include <ctime>
#include <cstdio>
using namespace std;

int main()
{
	printf("--set使用 by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
	const int MAXN = 15;
	int a[MAXN];
	int
i; srand(time(NULL)); for (i = 0; i < MAXN; ++i) a[i] = rand() % (MAXN * 2); set<int> iset; set<int>::iterator pos; //插入資料 insert()有三種過載 iset.insert(a, a + MAXN); //當前集合中個數 最大容納資料量 printf("當前集合中個數: %d 最大容納資料量: %d\n", iset.size(), iset.max_size()); //依次輸出 printf("依次輸出集合中所有元素-------\n"
); for (pos = iset.begin(); pos != iset.end(); ++pos) printf("%d ", *pos); putchar('\n'); //查詢 int findNum = MAXN; printf("查詢 %d是否存在-----------------------\n", findNum); pos = iset.find(findNum); if (pos != iset.end()) printf("%d 存在\n", findNum); else printf("%d 不存在\n", findNum); //在最後位置插入資料,如果給定的位置不正確,會重新找個正確的位置並返回該位置 pos = iset.insert(--iset.end(), MAXN * 2); printf("已經插入%d\n", *pos); //刪除 iset.erase(MAXN); printf("已經刪除%d\n", MAXN); //依次輸出 printf("依次輸出集合中所有元素-------\n"); for (pos = iset.begin(); pos != iset.end(); ++pos) printf("%d ", *pos); putchar('\n'); return 0; }

執行結果如下:

 

下面試下在set中使用類(結構體也可以類似這樣做)。這個類很簡單,只有一個成員變數,及設定和獲取這個成員變數的成員函式。

//在set中使用類要過載‘<’並實現拷貝建構函式
// by MoreWindows( http://blog.csdn.net/MoreWindows )
#include <set>
#include <ctime>
#include <cstdio>
using namespace std;
class Node
{
public:
	Node(int nAge = 0)
	{
		m_nAge = nAge;
	}
	Node(const Node &na)  //拷貝建構函式
	{
		m_nAge = na.GetAge();
	}
	int GetAge()
	{
		return m_nAge;
	}
private:
	int m_nAge;
};
//不能寫成類的成員函式
inline bool operator < (const Node &na, const Node &nb) 
{
	return na.GetAge() < nb.GetAge();
}
int main()
{
	int i;
	set<Node> nset;
	for (i = 0; i < MAXN; ++i)
		nset.insert(Node(i));
	return 0;
}

編譯,直接報了3個錯誤!!1個在拷貝建構函式,2個在operator<()函式。如下圖所示:

3個錯誤都是一樣的:

error C2662: “Node::GetAge”: 不能將“this”指標從“const Node”轉換為“Node &” 轉換丟失限定符

這是怎麼回事呀?分析下,拷貝建構函式與operator<()函數出錯,錯誤都指向了GetAge()函式,有點古怪,比較下它們與GetAge()函式,可以發現最大的不同點在於這2個函式都用到了const而GetAge()函式沒有使用const。難道是這個導致報錯了嗎?先給GetAge()函式加個const看看,如下:

       int GetAge()  const //增加這個const

       {

              returnm_nAge;

       }

再編譯,不報錯了。再查下資料,原因如下——因為那2個函式都使用了const修飾的物件,但GetAge()沒有加上const以保證它不修改物件,編譯器認為這種寫法是不安全的,所以就毫不猶豫報了個錯誤。

這種錯誤如果不親身體會下,到筆試面試時很可能寫了個錯誤程式而自己還處於一無所知中(死在這些小細節上最不值得)。另外,如果使用VC6.0則不會提示詳細的錯誤資訊——“轉換丟失限定符”。

 

STL還為set提供了一些集合運算的函式,如交集set_intersection()、並集set_union()、差集set_difference()和對稱差集set_symmetric_difference()。這些就不詳細介紹了,有興趣可以自己動手試一試。

下面開始對set和hash_set作個性能測試(Win7 +VS2008Release下)。

測試程式碼如下:

// by MoreWindows( http://blog.csdn.net/MoreWindows )
#include <set>
#include <hash_set>
#include <iostream>
#include <ctime>
#include <cstdio>
#include <cstdlib>
using namespace std;
using namespace stdext;  //hash_set

// MAXN個數據 MAXQUERY次查詢
const int MAXN = 10000, MAXQUERY = 5000000;
int a[MAXN], query[MAXQUERY];

void PrintfContainertElapseTime(char *pszContainerName, char *pszOperator, long lElapsetime)
{
	printf("%s 的%s操作 用時 %d毫秒\n", pszContainerName, pszOperator, lElapsetime);
}

int main()
{
	printf("set VS hash_set 效能測試 資料容量 %d個 查詢次數 %d次\n", MAXN, MAXQUERY);
	const int MAXNUM = MAXN * 4;
	const int MAXQUERYNUM = MAXN * 4;
	printf("容器中資料範圍 [0, %d) 查詢資料範圍[0, %d)\n", MAXNUM, MAXQUERYNUM);
	printf("--by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
	
	//隨機生成在[0, MAXNUM)範圍內的MAXN個數
	int i;
	srand(time(NULL));
	for (i = 0; i < MAXN; ++i)
		a[i] = (rand() * rand()) % MAXNUM;
	//隨機生成在[0, MAXQUERYNUM)範圍內的MAXQUERY個數
	srand(time(NULL));
	for (i = 0; i < MAXQUERY; ++i)
		query[i] = (rand() * rand()) % MAXQUERYNUM;

	set<int>       nset;
	hash_set<int> nhashset;
	clock_t  clockBegin, clockEnd;


	//insert
	printf("-----插入資料-----------\n");

	clockBegin = clock();  
	nset.insert(a, a + MAXN); 
	clockEnd = clock();
	printf("set中有資料%d個\n", nset.size());
	PrintfContainertElapseTime("set", "insert", clockEnd - clockBegin);

	clockBegin = clock();  
	nhashset.insert(a, a + MAXN); 
	clockEnd = clock();
	printf("hash_set中有資料%d個\n", nhashset.size());
	PrintfContainertElapseTime("hase_set", "insert", clockEnd - clockBegin);


	//find
	printf("-----查詢資料-----------\n");

	int nFindSucceedCount, nFindFailedCount; 
	nFindSucceedCount = nFindFailedCount = 0;
	clockBegin = clock(); 
	for (i = 0; i < MAXQUERY; ++i)
		if (nset.find(query[i]) != nset.end())
			++nFindSucceedCount;
		else
			++nFindFailedCount;
	clockEnd = clock();
	PrintfContainertElapseTime("set", "find", clockEnd - clockBegin);
	printf("查詢成功次數: %d    查詢失敗次數: %d\n", nFindSucceedCount, nFindFailedCount);
	
	nFindSucceedCount = nFindFailedCount = 0;
	clockBegin = clock();  
	for (i = 0; i < MAXQUERY; ++i)
		if (nhashset.find(query[i]) != nhashset.end())
			++nFindSucceedCount;
		else
			++nFindFailedCount;
	clockEnd = clock();
	PrintfContainertElapseTime("hash_set", "find", clockEnd - clockBegin);
	printf("查詢成功次數: %d    查詢失敗次數: %d\n", nFindSucceedCount, nFindFailedCount);
	return 0;
}

在資料容量100萬,查詢次數500萬時,程式執行結果如下:

由於查詢的失敗次數太多,這次將查詢範圍變小使用再測試下:

由於結點過多,80多萬個結點,set的紅黑樹樹高約為19(2^19=524288,2^20=1048576),查詢起來還是比較費時的。hash_set在時間效能上比set要好一些,並且如果查詢成功的機率比較大的話,hash_set會有更好的表現。想知道為什麼hash_set會有優良的效能表現,請看繼集——《STL系列之九 探索hash_set》。

 

 

注1.   MSDN上講set的erase()是有返回值的,但在VS2008中檢視set的原始碼,erase()函式的三個過載版本中,有二個返回值都為void即無返回值,另一個返回size_type。 可以通過http://msdn.microsoft.com/zh-cn/library/8h4a3515(v=VS.90).aspx檢視MSDN上對set的erase()說明。

 

 

轉載請標明出處,原文地址:http://blog.csdn.net/morewindows/article/details/7029587

 

再分享一下我老師大神的人工智慧教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智慧的隊伍中來!https://www.cnblogs.com/captainbed