一,set和hash_set簡介ios
在STL中,set是以紅黑樹(RB-Tree)做爲底層數據結構的,hash_set是以哈希表(Hash table)做爲底層數據結構的。set能夠在時間複雜度爲O(logN)的狀況下插入,刪除和查找數據。hash_set操做的時間度則比較複雜,取決於哈希函數和哈希表的負載狀況。windows
二,SET使用範例(hash_set相似)數據結構
1 #include <set> 2 #include <ctime> 3 #include <cstdio> 4 using namespace std; 5 6 int main() 7 { 8 const int MAXN = 15; 9 int a[MAXN]; 10 int i; 11 srand(time(NULL)); 12 for (i = 0; i < MAXN; ++i) 13 a[i] = rand() % (MAXN * 2); 14 15 set<int> iset; 16 set<int>::iterator pos; 17 18 //插入數據 insert()有三種重載 19 iset.insert(a, a + MAXN); 20 21 //當前集合中個數 最大容納數據量 22 printf("當前集合中個數: %d 最大容納數據量: %d\n", iset.size(), iset.max_size()); 23 24 //依次輸出 25 printf("依次輸出集合中全部元素-------\n"); 26 for (pos = iset.begin(); pos != iset.end(); ++pos) 27 printf("%d ", *pos); 28 putchar('\n'); 29 30 //查找 31 int findNum = MAXN; 32 printf("查找 %d是否存在-----------------------\n", findNum); 33 pos = iset.find(findNum); 34 if (pos != iset.end()) 35 printf("%d 存在\n", findNum); 36 else 37 printf("%d 不存在\n", findNum); 38 39 //在最後位置插入數據,若是給定的位置不正確,會從新找個正確的位置並返回該位置 40 pos = iset.insert(--iset.end(), MAXN * 2); 41 printf("已經插入%d\n", *pos); 42 43 //刪除 44 iset.erase(MAXN); 45 printf("已經刪除%d\n", MAXN); 46 47 //依次輸出 48 printf("依次輸出集合中全部元素-------\n"); 49 for (pos = iset.begin(); pos != iset.end(); ++pos) 50 printf("%d ", *pos); 51 putchar('\n'); 52 return 0; 53 }
運行結果函數
三,SET與HASH_SET性能對比性能
1 #include <set> 2 #include <hash_set> 3 #include <iostream> 4 #include <ctime> 5 #include <cstdio> 6 #include <cstdlib> 7 using namespace std; 8 using namespace stdext; //hash_set 9 10 // MAXN個數據 MAXQUERY次查詢 11 const int MAXN = 10000, MAXQUERY = 5000000; 12 int a[MAXN], query[MAXQUERY]; 13 14 void PrintfContainertElapseTime(char *pszContainerName, char *pszOperator, long lElapsetime) 15 { 16 printf("%s 的%s操做 用時 %d毫秒\n", pszContainerName, pszOperator, lElapsetime); 17 } 18 19 int main() 20 { 21 printf("set VS hash_set 性能測試 數據容量 %d個 查詢次數 %d次\n", MAXN, MAXQUERY); 22 const int MAXNUM = MAXN * 4; 23 const int MAXQUERYNUM = MAXN * 4; 24 printf("容器中數據範圍 [0, %d) 查詢數據範圍[0, %d)\n", MAXNUM, MAXQUERYNUM); 25 26 //隨機生成在[0, MAXNUM)範圍內的MAXN個數 27 int i; 28 srand(time(NULL)); 29 for (i = 0; i < MAXN; ++i) 30 a[i] = (rand() * rand()) % MAXNUM; 31 //隨機生成在[0, MAXQUERYNUM)範圍內的MAXQUERY個數 32 srand(time(NULL)); 33 for (i = 0; i < MAXQUERY; ++i) 34 query[i] = (rand() * rand()) % MAXQUERYNUM; 35 36 set<int> nset; 37 hash_set<int> nhashset; 38 clock_t clockBegin, clockEnd; 39 40 41 //insert 42 printf("-----插入數據-----------\n"); 43 44 clockBegin = clock(); 45 nset.insert(a, a + MAXN); 46 clockEnd = clock(); 47 printf("set中有數據%d個\n", nset.size()); 48 PrintfContainertElapseTime("set", "insert", clockEnd - clockBegin); 49 50 clockBegin = clock(); 51 nhashset.insert(a, a + MAXN); 52 clockEnd = clock(); 53 printf("hash_set中有數據%d個\n", nhashset.size()); 54 PrintfContainertElapseTime("hase_set", "insert", clockEnd - clockBegin); 55 56 57 //find 58 printf("-----查詢數據-----------\n"); 59 60 int nFindSucceedCount, nFindFailedCount; 61 nFindSucceedCount = nFindFailedCount = 0; 62 clockBegin = clock(); 63 for (i = 0; i < MAXQUERY; ++i) 64 if (nset.find(query[i]) != nset.end()) 65 ++nFindSucceedCount; 66 else 67 ++nFindFailedCount; 68 clockEnd = clock(); 69 PrintfContainertElapseTime("set", "find", clockEnd - clockBegin); 70 printf("查詢成功次數: %d 查詢失敗次數: %d\n", nFindSucceedCount, nFindFailedCount); 71 72 nFindSucceedCount = nFindFailedCount = 0; 73 clockBegin = clock(); 74 for (i = 0; i < MAXQUERY; ++i) 75 if (nhashset.find(query[i]) != nhashset.end()) 76 ++nFindSucceedCount; 77 else 78 ++nFindFailedCount; 79 clockEnd = clock(); 80 PrintfContainertElapseTime("hash_set", "find", clockEnd - clockBegin); 81 printf("查詢成功次數: %d 查詢失敗次數: %d\n", nFindSucceedCount, nFindFailedCount); 82 return 0; 83 }
運行結果以下:測試
因爲查詢的失敗次數太多,此次將查詢範圍變小使用再測試下:大數據
因爲結點過多,80多萬個結點,set的紅黑樹樹高約爲19(2^19=524288,2^20=1048576),查詢起來仍是比較費時的。hash_set在時間性能上比set要好一些,而且若是查詢成功的概率比較大的話,hash_set會有更好的表現。ui
四,深刻分析hash_set編碼
1. hash tablespa
hash_set的底層數據結構是哈希表,所以要深刻了解hash_set,必須先分析哈希表。 hash表的出現主要是爲了對內存中數據的快速、隨機的訪問。它主要有三個關鍵點:Hash表的大小、Hash函數、衝突的解決。哈希表是根據關鍵碼值(Key-Value)而直接進行訪問的數據結構,它用哈希函數處理數據獲得關鍵碼值,關鍵碼值對應表中一個特定位置再由應該位置來訪問記錄,這樣能夠在時間複雜性度爲O(1)內訪問到數據。可是頗有可能出現多個數據經哈希函數處理後獲得同一個關鍵碼——這就產生了衝突,解決衝突的方法也有不少,各大數據結構教材及考研輔導書上都會介紹大把方法。這裏採用最方便最有效的一種——鏈地址法,當有衝突發生時將具同一關鍵碼的數據組成一個鏈表。下圖展現了鏈地址法的使用:
2. 關於Hash表的大小
Hash表的大小通常是定長的,若是太大,則浪費空間,若是過小,衝突發生的機率變大,體現不出效率。因此,選擇合適的Hash表的大小是Hash表性能的關鍵。
對於Hash表大小的選擇一般會考慮兩點:
第一,確保Hash表的大小是一個素數。常識告訴咱們,當除以一個素數時,會產生最分散的餘數,可能最糟糕的除法是除以2的倍數,由於這隻會屏蔽被除數中的位。因爲咱們一般使用表的大小對hash函數的結果進行模運算,若是表的大小是一個素數,就能夠得到最佳的結果。
第二,建立大小合理的hash表。這就涉及到hash表的一個概念:裝填因子。設裝填因子爲a,則:
a=表中記錄數/hash表表長
一般,咱們關注的是使hash表的平均查找長度最小,而平均查找長度是裝填因子的函數,而不是表長n的函數。a的取值越小,產生衝突的機會就越小,但若是a取值太小,則會形成較大的空間浪費,一般,只要a的取值合適,hash表的平均查找長度就是一個常數,即hash表的平均查找長度爲O(1)。
固然,根據不一樣的數據量,會有不一樣的哈希表的大小。對於數據量時多時少的應用,最好的設計是使用動態可變尺寸的哈希表,那麼若是你發現哈希表尺寸過小了,好比其中的元素是哈希表尺寸的2倍時,咱們就須要擴大哈希表尺寸,通常是擴大一倍。
下面是哈希表尺寸大小的可能取值(素數,後邊是前邊的2倍左右):
17, 37, 79, 163, 331, 673, 1361, 2729, 5471, 10949, 21911, 43853, 87719, 175447, 350899,701819, 1403641, 2807303, 5614657, 11229331, 22458671, 44917381, 89834777, 179669557, 359339171, 718678369, 1437356741, 2147483647
那麼C++的STL中hash_set是如何實現動態增長哈希表長度的呢?
首先來看看VS2008中hash_set是如何實現動態的增長表的大小,hash_set是在hash_set.h中聲明的,在hash_set.h中能夠發現hash_set是繼承_Hash類的,hash_set自己並無太多的代碼,只是對_Hash做了進一步的封裝,這種作法在STL中很是常見,如stack棧和queue單向隊列都是以deque雙向隊列做底層數據結構再加一層封裝。
_Hash類的定義和實現都在xhash.h類中,微軟對_Hash類的第一句註釋以下——
hash table -- list with vector of iterators for quick access。
這說明_Hash實際上就是由vector和list組成哈希表。再閱讀下代碼能夠發現_Hash類增長空間由_Grow()函數完成,當空間不足時就倍增(或者近2被的素數),而且表中原有數據都要從新計算hash值以肯定新的位置。也就是從新申請一個更大的空間,同時將原來hash_set中的值逐個放到新的hash_set中。
3. 哈希函數
關鍵字
|
內部編碼
|
內部編碼的平方值
|
H(k)關鍵字的哈希地址
|
KEYA
|
11050201
|
122157778355001
|
778
|
KYAB
|
11250102
|
126564795010404
|
795
|
AKEY
|
01110525
|
001233265775625
|
265
|
BKEY
|
02110525
|
004454315775625
|
315
|
參考文章
http://blog.csdn.net/morewindows/article/details/7029587
http://blog.csdn.net/morewindows/article/details/7330323
http://blog.csdn.net/qll125596718/article/details/6997850
http://baike.baidu.com/view/329976.htm?fromtitle=%E6%95%A3%E5%88%97%E8%A1%A8&fromid=10027933&type=syn