1. 程式人生 > 實用技巧 >c++ set與unordered set的區別

c++ set與unordered set的區別

c++ std中set與unordered_set區別和map與unordered_map區別類似,其底層的資料結構說明如下:

  1、set基於紅黑樹實現,紅黑樹具有自動排序的功能,因此map內部所有的資料,在任何時候,都是有序的。

  2、unordered_set基於雜湊表,資料插入和查詢的時間複雜度很低,幾乎是常數時間,而代價是消耗比較多的記憶體,無自動排序功能。底層實現上,使用一個下標範圍比較大的陣列來儲存元素,形成很多的桶,利用hash函式對key進行對映到不同區域進行儲存。

更詳細的區別,如下圖:

set與unordered相比:

  1、set比unordered_set使用更少的記憶體來儲存相同數量的元素。

  2、對於少量的元素,在set中查詢可能比在unordered_set中查詢更快。

  3、儘管許多操作在unordered_set的平均情況下更快,但通常需要保證set在最壞情況下有更好的複雜度(例如insert)。

  4、如果您想按順序訪問元素,那麼set對元素進行排序的功能是很有用的。

  5、您可以用<、<=、>和>=從字典順序上比較不同的set集。unordered_set集則不支援這些操作。

一般來說,在如下情況,適合使用set:

  1、我們需要有序的資料(不同元素)。

  2、我們必須列印/訪問資料(按排序順序)。

  3、我們需要知道元素的前任/繼承者。

一般來說,在如下情況,適合使用unordered_set:

  1、我們需要保留一組元素,不需要排序。

  2、我們需要單元素訪問,即不需要遍歷。

  3、僅僅只是插入、刪除、查詢的話。

示例:

set:

Input : 1, 8, 2, 5, 3, 9
Output : 1, 2, 3, 5, 8, 9

unordered_set:

Input : 1, 8, 2, 5, 3, 9
Output : 9 3 1 8 2 5 (順序依賴於 hash function)

下面再給出一個以vector<int>為key的示例,對比下set與unordered_set:

1
set<vector<int>> s; 2 s.insert({1, 2}); 3 s.insert({1, 3}); 4 s.insert({1, 2}); 5 6 for(const auto& vec:s) 7 cout<<vec<<endl; 8 // 1 2 9 // 1 3

因為vector過載了operator<,因此可以作為set的key。

但是如果直接使用unordered_set<vector<int>> s;則報錯,因為vector沒有hash函式,需要自己定義一個,可以定義一個類似下面這樣的hash函式:

 1 struct VectorHash {
 2     size_t operator()(const std::vector<int>& v) const {
 3         std::hash<int> hasher;
 4         size_t seed = 0;
 5         for (int i : v) {
 6             seed ^= hasher(i) + 0x9e3779b9 + (seed<<6) + (seed>>2);
 7         }
 8         return seed;
 9     }
10 };

接下來這樣使用:

1 unordered_set<vector<int>, VectorHash> s;
2 s.insert({1, 2});
3 s.insert({1, 3});
4 s.insert({1, 2});
5 
6 for(const auto& vec:s)
7     cout<<vec<<endl;
8 // 1 2
9 // 1 3

或者模板特化struct hash<std::vector<int>>

 1 namespace std {
 2     template<>
 3     struct hash<std::vector<int>> {
 4         size_t operator()(const vector<int> &v) const {
 5             std::hash<int> hasher;
 6             size_t seed = 0;
 7             for (int i : v) {
 8                 seed ^= hasher(i) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
 9             }
10             return seed;
11         }
12     };
13 }
14 
15 // usage example
16 void test_unordered_set(){
17     unordered_set<std::vector<int>> s;
18     s.insert({1, 2});
19     s.insert({1, 3});
20     s.insert({1, 2});
21     for(const auto& vec:s)
22         cout<<vec<<endl;
23     //    1 3
24     //    1 2
25 
26     std::hash<int> hasher;
27     cout<<"hasher(99): "<<hasher(99)<<" ,hasher(77): "<<hasher(77)<<endl;
28     // hasher(99): 99 ,hasher(77): 77
29 }

可以看到,在某些情況下,unordered_set的使用門檻還是挺高的。

Input : 1, 8, 2, 5, 3, 9
Output : 1, 2, 3, 5, 8, 9