可以根據散列表解決散列沖突的方法把散列表分成開散列表和閉散列表:
當發生散列沖突時,使用開放尋址法把沖突的數據項放入別的空的單元中,稱為閉散列表,其中尋址方法有線性探測法,平方探測法等。
當發生散列沖突時,使用拉鏈法,把映射到同一地址的數據元素組織成鏈表,稱為開散列表
閉散列表的裝載因子不能大於1,且一般當裝載因子大於0.75時可認為將會產生大量散列沖突導致散列表性能下降。因此閉散列表在裝在因子大於0.75時需要啟動擴容機制。
閉散列表擴容的方法有兩種,第一種為一次性擴容,即申請一個兩倍於原散列表大小的新表,把所有數據搬移至新表中。但是這種擴容機制會讓個別幾次插入操作的時間特別長(即剛好需要擴容的那幾次),導致性能不夠穩定。第二種方法為動態擴容,即同時維護兩個表,每次插入操作都只把原表中的一項數據移動到新表,動態擴容的好處是幾乎每次插入操作的時間都差不多。
開散列表的裝載因子可以大於1,但是當一個單元中的元素過多時遍歷鏈表也會增加操作的時間復雜度。一般的做法是當一個單元中的鏈表長度大於8時把鏈表轉換成紅黑樹以降低查詢的時間復雜度。
以線性探測法解決散列沖突的閉散列表代碼如下(采用的是一次性擴容機制)
#include <iostream> using namespace std; // 鍵值對 template<class KEY, class VALUE> struct PAIR{ KEY key; VALUE value; PAIR(){} PAIR(KEY k, VALUE v):key(k), value(v){} }; // 動態查找表抽象類 template<class KEY, class VALUE> class dynamicSearchTable{ public: virtual PAIR<KEY, VALUE>* find(const KEY &x) const = 0; // 根據鍵在哈希表中查找鍵值對 virtual bool insert(const PAIR<KEY, VALUE> &x) = 0; // 往哈希表中插入一個鍵值對 virtual bool remove(const KEY &x) = 0; // 根據鍵在哈希表中刪除鍵值對 virtual ~dynamicSearchTable(){}; }; // 基於線性探測法的閉散列表的實現 template<class KEY, class VALUE> class closeHashTable:public dynamicSearchTable<KEY, VALUE>{ private: struct node{ // 哈希表中用到的節點 PAIR<KEY, VALUE> data; int state; // 節點狀態 0:空, 1:占用, 2:被刪除 node(){state = 0;} }; node* array; // 哈希表儲存 int MaxSize; // 哈希表的儲存上限 int CurrentSize; // 當前哈希表已儲存的節點數 bool expansion(); // 擴容函數,這里使用的是一次性擴容,實際上還可以使用均攤法動態擴容 int (*Hash)(const KEY &x); // 指向哈希函數的指針,該指針可以指向所有參數表為const KEY & x返回值為int的函數 static int defaultHash(const KEY &x){return int(x);} // 默認哈希函數 public: closeHashTable(int size = 101, int (*f)(const KEY & x) = defaultHash); ~closeHashTable(){delete []array;} PAIR<KEY, VALUE>* find(const KEY &x) const; bool insert(const PAIR<KEY, VALUE> &x); bool remove(const KEY &x); }; template<class KEY, class VALUE> bool closeHashTable<KEY, VALUE>::expansion(){ // 一次性擴容 int newMaxSize = MaxSize * 2; node* newarray; try{newarray = new node[newMaxSize];} catch(bad_alloc &memExp){return false;} for(int i = 0; i < MaxSize; ++i){ if(array[i].state == 1){ int pos = Hash(array[i].data.key) % newMaxSize; while(newarray[pos].state == 1) pos = (pos + 1) % newMaxSize; newarray[pos].data = array[i].data; newarray[pos].state = 1; } } MaxSize = newMaxSize; delete []array; array = newarray; return true; } template<class KEY, class VALUE> closeHashTable<KEY, VALUE>::closeHashTable(int size, int (*f)(const KEY &x)){ MaxSize = size; CurrentSize = 0; array = new node[MaxSize]; Hash = f; } template<class KEY, class VALUE> bool closeHashTable<KEY, VALUE>::insert(const PAIR<KEY, VALUE> &x){ if(CurrentSize / MaxSize > 0.75 && !expansion()) return false; int initPos, Pos; initPos = Pos = Hash(x.key) % MaxSize; do{ if(array[Pos].state == 1) Pos = (Pos + 1) % MaxSize; else break; }while(Pos != initPos); array[Pos].data = x; array[Pos].state = 1; ++ CurrentSize; return true; } template<class KEY, class VALUE> PAIR<KEY, VALUE>* closeHashTable<KEY, VALUE>::find(const KEY &x) const{ int initPos, Pos; initPos = Pos = Hash(x) % MaxSize; do{ if(array[Pos].state == 0) break; if(array[Pos].state == 1 && array[Pos].data.key == x) return (PAIR<KEY, VALUE>*) &array[Pos]; Pos = (Pos + 1) % MaxSize; }while(Pos != initPos); return NULL; } template<class KEY, class VALUE> bool closeHashTable<KEY, VALUE>::remove(const KEY &x){ // 哈希表的刪除都是遲刪除,而不能是真正的刪除 int initPos, Pos; initPos = Pos = Hash(x) % MaxSize; do{ if(array[Pos].state == 0) break; if(array[Pos].state == 1 && array[Pos].data.key == x){ array[Pos].state = 2; --CurrentSize; return true; } }while(initPos != Pos); return false; }
可以利用LeetCode上的第一題兩數之和測試上面實現的哈希表,代碼如下(題目詳見leetcode)
int H(const int & x){ //自定義哈希函數 return abs(int(x*13.33261)); } class Solution { public: vector<int> twoSum(vector<int>& nums, int target) { closeHashTable<int, int> a(101, H); // 創建空哈希表 for(int i = 0; i < nums.size(); ++i) // 把vector中的數據都插入哈希表 a.insert(PAIR<int, int>(nums[i], i)); for(int i = 0; i < nums.size(); ++i){ a.remove(nums[i]); //先在哈希表中刪除第一個當前檢測的數據以免重復取 PAIR<int, int>* p = a.find(target - nums[i]); if(p != NULL) return vector<int>{i, p->value}; a.insert(PAIR<int, int>(nums[i], i)); //再把刪除的重新插入 } return vector<int>{0}; } };
這里的自定義哈希函數乘了一個隨意的系數之后,運行時間從760ms下降到12ms,可見碰撞問題多影響哈希表的效率。