例說資料結構&STL(十)——hash_set/unordered_set

無鞋童鞋發表於2017-07-30

1 白話hash_set/unordered_set
  這一章節,我們來了解兩個新的結構體hash_set和unorderd_set。我將這兩者放在一個博文中介紹是因為它們都屬於基於雜湊表(hash table)構建的資料結構,並且是關鍵字與鍵值相等的關聯容器。後面我們還會介紹到hash_map與unordered_map兩種資料結構,這就好比是set與map的區別了,後面我們再說。
  說到這那到底hash_set與unordered_set哪個更好呢?實際上unordered_set在C++11的時候被引入標準庫了,而hash_set並沒有,所以建議還是使用unordered_set比較好,這就好比一個是官方認證的,一個是民間流傳的。在編譯器中,Visual Studio(當然需要支援C++11的版本)庫中兩個資料結構都有定義,而在gcc/g++中並不支援hash_set。總之,如果想使用這種基於雜湊表的關聯容器,那麼就使用unordered_set就好了。下面我們也將會圍繞無序集合容器(unordered_set)講解,hash_set有對應的公共介面不再細說。
  此外我們來看看unordered_set/hash_set與set有什麼區別。首先從內部構建來看,雖然都屬於關鍵字與鍵值相等的關聯容器,但是內部結構大大的不同。set的內部結構是基於紅黑樹來實現的,所以保證了一個穩定的動態操作時間,查詢、插入、刪除都是O(logN),最壞和平均都是。而unordered_map如前所述,是雜湊表。順便提一下,雜湊表的查詢時間雖然是O(1),但是並不是unordered_map查詢時間一定比map短,因為實際情況中還要考慮到資料量,而且unordered_map的hash函式的構造速度也沒那麼快,所以不能一概而論,應該具體情況具體分析。
  第二點從儲存方式來看,unordered_set也是一個儲存唯一(unique,即無重複)的關聯容器(Associative container),但是容器中的元素無特別的秩序關係,該容器允許基於值的快速元素檢索,同時也支援正向迭代。在一個unordered_set內部,元素不會按任何順序排序,而是通過元素值的hash值將元素分組放置到各個桶中,這樣就能通過元素值快速訪問各個對應的元素(均攤耗時為O(1))。
2 小談雜湊表
  hash_set/unordered_set是雜湊表構建的,所以我們在介紹其方法介面前還是有進一步瞭解一下雜湊表的原理。
  雜湊表是根據關鍵碼值而進行直接訪問的資料結構,通過相應的雜湊函式(也稱雜湊函式)處理關鍵字得到相應的關鍵碼值,關鍵碼值對應著一個特定位置,用該位置來存取相應的資訊,這樣就能以較快的速度獲取關鍵字的資訊。
  比如:現有公司員工的個人資訊(包括年齡),需要查詢某個年齡的員工個數。由於人的年齡範圍大約在[0,200],所以可以開一個200大小的陣列,然後通過雜湊函式得到key對應的key-value,這樣就能完成統計某個年齡的員工個數。而在這個例子中,也存在這樣一個問題,兩個員工的年齡相同,但其他資訊(如:名字、身份證)不同,通過前面說的雜湊函式,會發現其都位於陣列的相同位置,這裡,就涉及到“衝突”。準確來說,衝突是不可避免的,而解決衝突的方法常見的有:開發地址法、再雜湊法、鏈地址法(也稱拉鍊法)。而unordered_set內部解決衝突採用的是鏈地址法,當用衝突發生時把具有同一關鍵碼的資料組成一個連結串列。下圖展示了鏈地址法的使用:

這裡寫圖片描述

3 unordered_set實戰
 3.1 標頭檔案

#include<unordered_set> // hash_set則是#iunclude<hash_set>

using namespace std;

 3.2 其他操作
  由於其常用方法介面和set幾乎一樣,我不在過多描述,下面只貼出程式樣例,一些說明請閱讀博文例說資料結構&STL(八)——set.

    unordered_set<int> set_fir; // 預設構造物件

    unordered_set<int> set_sed = { 2, 3, 10, 5, 9 }; //初始化構造

    set_sed.insert(7);          // 插入7,放置在set中位置跟hash構建有關,並不是在尾部

    unordered_set<int>::iterator iter1 = set_sed.lower_bound(2); //返回set中>=2的索引(迭代器),切記非小於2

    unordered_set<int>::iterator iter2 = set_sed.upper_bound(2); //返回set中>2的索引

    set_sed.erase(2); //刪除set中元素2

    set_sed.erase(set_sed.begin(), set_sed.end()); //清空整個set

    if (set_sed.find(5) != set_sed.end()) // 查詢鍵值為5的元素
        cout << "exsit" << endl;

    // 正向訪問
    unordered_set<int>::iterator iter4;
    for (iter4 = set_sed.begin(); iter4 != set_sed.end(); iter4++)
        cout << *iter4 << endl;

    unordered_set<int>::reverse_iterator iter5; //對應反向迭代器物件
    // 反向訪問
    for (iter5 = set_sed.rbegin(); iter5 != set_sed.rend(); iter5++)
        cout << *iter5 << endl;

    set_sed.count(12);     // 返回set中元素的個數,由於set的特殊性,所以結果只有0或者1

    set_sed.swap(set_fir); // 交換所有資料,需要確保set中元素型別相同

    set_sed.clear();       // 清空集合set_sed

    set_sed.size();        // 統計set_sed中元素個數

    set_sed.empty();       // 判斷set中是否為空,空則返回1

4 小結
  上面介紹了無序集合容器資料結構特點以及公用的介面。由於集合是基於雜湊表構建的資料結構,所以其查詢的時間複雜度只有O(1),n為集合中元素的個數。
  以上是個人學習記錄,由於能力和時間有限,如果有錯誤望讀者糾正,謝謝!
  轉載請註明出處:http://blog.csdn.net/FX677588/article/details/76400389


  參考文獻:
  http://www.cnblogs.com/davidgu/p/4998083.html
  http://blog.csdn.net/vevenlcf/article/details/51743058
  http://blog.csdn.net/sdnu111111111/article/details/38658929

相關文章