寫在前面的話,原本看網上的面經就一直有關於哈希表的問題,再加之實驗室同窗頭條面試的時候讓實現一個unordered_map,原本已經把對哈希表的總結和實現提上日程了。奈何太懶,一天拖一天,直到本身面阿里的時候被面試官在哈希表上翻來覆去蹂躪的時候,真的是不得不感嘆一句,活該!!!node
業精於勤..git
哈希表,也稱散列表,是實現字典操做的一種有效的數據結構。儘管最壞狀況下,散列表查找一個元素的時間與鏈表中查找的時間相同,達到了O(n)。然而在實際應用中,散列表查找的性能是極好的。在一些合理的假設下,在散列表中能夠查找一個元素的平均時間是O(1)github
哈希表的精髓在於哈希二字上面,也就是數學裏面經常使用到的映射關係。它是經過哈希函數將關鍵字映射到表中的某個位置上進行存放,以實現快速插入和查詢的。面試
爲何須要哈希函數?簡單來說,解決存儲空間的考慮。試想,將100個關鍵字存入大小爲100的數組裏,此時確定是不須要哈希函數的,一對一的放,確定是能夠實現的。可是當數據量增大,將1000個關鍵字,存入大小爲100的數組裏呢?此時一個一個的放,那剩下的怎麼辦呢,因此,咱們須要某種計算方法,既能把這1000個關鍵字存進去,並且最主要是還能取出來。這就是哈希函數要作的事,給每個關鍵字找一個合適的位置,讓你既能存進去,還能把它取出來。注意,哈希表裏通常存放的字典類型數據,即(key, value)的數據,是根據key去存取value。數組
經過哈希函數去計算哈希值,不免會有衝突的時候,解決衝突的方法有以下幾種:數據結構
在使用開鏈法解決衝突問題時,將哈希表內的元素稱爲桶(bucket),大約意義是,表格內的每一個單元,涵蓋的不僅是個節點(元素),甚至多是一「桶」節點。函數
假設哈希表中共有M個元素(桶),編號爲0,1,..,M-1。 如今哈希函數要作的就是將關鍵字映射到這M個桶中,儘可能保證均勻。性能
最經常使用的是除留餘數法計算哈希值:用一個特定的質數來除所給定的關鍵字,所得餘數即爲該關鍵字的哈希值。測試
在此,仿STL的hashtable實現一個簡化版的哈希表,做爲本文的結束。this
採用開鏈法處理衝突,而後hashtable以vector做爲底層數組,鍵值類型的話,直接用template吧
哈希表節點定義以下:
template<class Value> struct hashtable_node{ hashtable_node *next; Value val; };
桶裏的鏈表也本身實現,不使用STL裏面提供的list,算是熟悉熟悉單鏈表吧。
首先理清哈希表須要的模板類型,Key, Value
只作最簡單的(Key, Value), Key的類型考慮char, int, double, string
下面給出哈希表的定義,本文只考慮幾個比較經常使用的操做,即插入,刪除,查找,返回大小,最後再加上一個打印哈希表的函數,具體定義以下:
template<class Key, class Value> class hashtable{ public: //哈希表節點鍵值類型 typedef pair<Key, Value> T; //表節點 typedef hashtable_node<T> node; public: //構造函數 hashtable(); hashtable(hashtable<Key, Value> &ht) : buckets(ht.buckets), num_elements(ht.num_elements) {} //插入一個關鍵字 void insert(T kv); //根據鍵值刪除關鍵字 void erase(Key key); //判斷關鍵字是否在哈希表中 bool find(Key key); //返回哈希表中關鍵字個數 int size(){ return num_elements; } void printHashTable(); private: //根據傳入大小判斷是否須要從新分配哈希表 void resize(int num_elements); //根據鍵值返回桶的編號 int buckets_index(Key key, int size){ return hash(key) % size; } //根據節點返回鍵值 Key get_key(T node){ return node.first; } private: //使用STL list<T>做桶 vector<node*> buckets; //哈希表中元素個數 size_t num_elements; //哈希函數 hashFunc<Key> hash; };
哈希函數的設計,因爲只考慮了char, int, double, string四種類型,在使用模板類的話,能夠經過template的偏特化特性直接爲這四種類型設計特化版本。相關代碼以下
/* * 哈希函數的設定,只考慮 4 種鍵值類型的哈希函數 * char, int , double , string */ template<class Key> struct hashFunc{}; template<> struct hashFunc < char > { size_t operator()(char x) const { return x; } }; template<> struct hashFunc < int > { size_t operator()(int x) const { return x; } }; template<> struct hashFunc < double > { size_t operator()(const double & dValue) const { int e = 0; double tmp = dValue; if (dValue<0) { tmp = -dValue; } e = ceil(log(dValue)); return size_t((INT64_MAX + 1.0) * tmp * exp(-e)); } }; template<> struct hashFunc < string > { size_t operator()(const string & str) const { size_t h = 0; for (size_t i = 0; i<str.length(); ++i) { h = (h << 5) - h + str[i]; } return h; } };
下面貼出哈希表的具體實現代碼吧,關於各個函數的實現,都給出了相關注釋,應該算是簡單易懂的。
//將表格的大小設爲質數,而後直接使用除留餘數法求哈希值 //按照SGI STL中的原則,首先保存28個質數(逐漸呈現大約兩倍的關係), //同時提供一個函數,用來查詢在這28個質數中,最接近某數並大於某數的質數 static const int num_primes = 28; static const unsigned long prime_list[num_primes] = { 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741, 3221225473, 4294967291 }; //找出最接近但大於的質數 inline unsigned long next_prime(unsigned long n){ const unsigned long *first = prime_list; const unsigned long *last = prime_list + num_primes; const unsigned long *pos = lower_bound(first, last, n); return pos == last ? *(last - 1) : *pos; } //構造函數,初始化哈希表 template<class Key, class Value> hashtable<Key, Value>::hashtable(){ const int n_buckets = next_prime(1); buckets.reserve(n_buckets); buckets.insert(buckets.end(), n_buckets, (node*)0); num_elements = 0; } //插入一個關鍵字 template<class Key, class Value> void hashtable<Key, Value>::insert(T kv){ //在插入以前,調用resize函數,判斷是否須要重建哈希表 resize(num_elements + 1); //計算出插入位置 int pos = buckets_index(kv.first, buckets.size()); node *head = buckets[pos]; //判斷鍵值是否已經存在,若存在,則直接返回,不插入 for (node *cur = head; cur; cur = cur->next){ if (cur->val.first == kv.first) return; } //分配節點,插入 node *tmp = new node(kv); tmp->next = head; buckets[pos] = tmp; num_elements++; //記錄個數 } //根據鍵值刪除關鍵字 template < class Key, class Value> void hashtable<Key, Value>::erase(Key key){ //找出桶的位置 int pos = buckets_index(key, buckets.size()); node *head = buckets[pos]; node *pre = NULL; while (head){ //查找到對應鍵,並刪除 if (head->val.first == key){ if (pre == NULL){ buckets[pos] = head->next; delete head; num_elements--; return; } else{ pre->next = head->next; delete head; num_elements--; return; } } pre = head; head = head->next; } } //根據鍵值,判斷是否在哈希表中 template<class Key, class Value> bool hashtable<Key, Value>::find(Key key){ int pos = buckets_index(key, buckets.size()); node *head = buckets[pos]; while (head){ if (head->val.first == key) return true; head = head->next; } return false; } template<class Key, class Value> void hashtable<Key, Value>::resize(int num_elements){ //當元素個數大於桶的個數時,從新分配哈希表 const int size = buckets.size(); if (num_elements <= size) return; //找出下一個質數 const int next_size = next_prime(num_elements); //初始化新的哈希表 vector<node*> tmp(next_size, (node*)0); for (int i = 0; i < size; ++i){ node *head = buckets[i]; while (head){ int new_pos = buckets_index(head->val.first, next_size); buckets[i] = head->next; head->next = tmp[new_pos]; tmp[new_pos] = head; head = buckets[i]; } } //交換新舊哈希表 buckets.swap(tmp); } template<class Key, class Value> void hashtable<Key, Value>::printHashTable(){ cout << "哈希表內容以下 :" << endl; for (int i = 0; i < buckets.size(); ++i){ node *head = buckets[i]; while (head){ cout << head->val.first << " " << head->val.second << endl; head = head->next; } } }
如下爲測試代碼:
#include"hashtable.h" int main() { //(int, string) 測試以下: hashtable<int, string> ht; ht.insert(pair<int, string>(12, "this")); ht.insert(pair<int, string>(3, "is")); ht.insert(pair<int, string>(58, "a")); ht.insert(pair<int, string>(10, "test")); ht.insert(pair<int, string>(23, "hashtable")); ht.printHashTable(); cout << "刪除(3, a)後,"; ht.erase(3); ht.printHashTable(); cout << "插入(3, hahaha)後,"; ht.insert(pair<int, string>(3, "hahaha")); ht.printHashTable(); cout << "===================================" << endl; //(string, string) 測試以下 hashtable<string, string> strHt; strHt.insert(pair<string, string>("hello", "world")); strHt.insert(pair<string, string>("other", "hash")); strHt.insert(pair<string, string>("test", "china")); strHt.insert(pair<string, string>("stl", "nimeiya")); strHt.printHashTable(); cout << "判斷 test 是否在哈希表中:" << strHt.find("test") << endl; cout << "返回此時哈希表中的元素個數:" << strHt.size() << endl; cout << "刪除test後" << endl; strHt.erase("test"); strHt.printHashTable(); cout << "判斷 test 是否在哈希表中:" << strHt.find("test") << endl; cout << "返回此時哈希表中的元素個數:" << strHt.size() << endl; }
測試結果如圖: