哈希(散列)技術既是一種存儲方法,也是一種查找方法。然而它與線性表、樹、圖等結構不一樣的是,前面幾種結構,數據元素之間都存在某種邏輯關係,能夠用連線圖示表示出來,而哈希技術的記錄之間不存在什麼邏輯關係,它只與關鍵字有關聯。所以,哈希主要是面向查找的存儲結構。哈希技術最適合的求解問題是查找與給定值相等的記錄。node
構造哈希函數的目標在於使哈希地址儘量均勻地分佈在連續的內存單元地址上,以減小發生衝突的可能性,同時使計算儘量簡單以達到儘量高的時間效率,這裏主要看看兩個構造哈希函數的方法。ios
(1)直接地址法算法
直接地址法取關鍵字的某個線性函數值爲哈希地址,即h(key)=key 或 h(key)=a*key+b數組
其中,a、b均爲常數,這樣的散列函數優勢就是簡單、均勻,也不會產生衝突,但問題是這須要事先知道關鍵字的分佈狀況,適合查找表較小且連續的狀況。因爲這樣的限制,在現實應用中,此方法雖然簡單,但卻並不經常使用。函數
(2)除留餘數法性能
除留餘數法採用取模運算(%)把關鍵字除以某個不大於哈希表表長的整數獲得的餘數做爲哈希地址,它也是最經常使用的構造哈希函數的方法,其形式爲:h(key)=key%p測試
本方法的關鍵就在於選擇合適的p,p若是選得很差,就可能會容易產生同義詞。spa
PS:根據前輩們的經驗,若哈希表表長爲m,一般p爲小於或等於表長(最好接近m)的最小質數或不包含小於20質因子的合數。指針
(1)閉散列法code
閉散列法時把全部的元素都存儲在哈希表數組中,當發生衝突時,在衝突位置的附近尋找可存放記錄的空單元。尋找「下一個」空位的過程則稱爲探測。上述方法可用以下公式表示爲:
其中,h(key)爲哈希函數,m爲哈希表長度,di爲遞增的序列。根據di的不一樣,又能夠分爲幾種探測方法:線性探測法、二次探測法以及雙重散列法。
(2)開散列法
開散列法的常見形式是將全部關鍵字爲同義詞的記錄存儲在一個單鏈表中。咱們稱這種表爲同義詞子表,在散列表中只存儲全部同義詞子表的頭指針。對於關鍵字集合{12,67,56,16,25,37,22,29,15,47,48,34},咱們用前面一樣的12爲除數,進行除留餘數法,可獲得以下圖所示的結構,此時,已經不存在什麼衝突換址的問題,不管有多少個衝突,都只是在當前位置給單鏈表增長結點的問題。
該方法對於可能會形成不少衝突的散列函數來講,提供了毫不會出現找不到地址的保障。固然,這也就帶來了查找時須要遍歷單鏈表的性能損耗。在.NET中,鏈表的各個元素分散於託管堆各處,也會給GC垃圾回收帶來壓力,影響程序的性能。
#pragma once // 開放定址法解決hash衝突 /* 1.pair<k,v> 2.vector<pair<k,v>> 3.使用素數對齊坐哈希表的容量,下降哈希衝突 4.將負載因子控制在0.7-0.8範圍內性能最高 5.支持任一類型hash算法 */ #include<string> #include <vector> #include<iostream> using namespace std; static size_t GetNextPrime(const size_t& value) { // 使用素數表對齊作哈希表的容量,下降哈希衝突 const int _PrimeSize = 28; static const unsigned long _PrimeList[_PrimeSize] = { 53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul }; for (size_t i = 0; i < _PrimeSize; ++i) { if (_PrimeList[i] > value) { return _PrimeList[i]; } } return _PrimeList[_PrimeSize - 1]; } template <class K> class CHashFunc { public: CHashFunc(){} size_t operator()(const K &key) { return key; } }; template<> // 顯示專用化須要 class CHashFunc<string> { public: size_t BKDRHash(const char * str) { unsigned int seed = 131; // 31 131 1313 13131 131313 unsigned int hash = 0; while (*str) { hash = hash * seed + (*str++); } return (hash & 0x7FFFFFFF); } size_t operator()(const string& key) { return BKDRHash(key.c_str()); } }; template<class K, class V,class HashFunc = CHashFunc<K>> class HashTab { public: HashTab() :m_nNumber(0){} typedef pair<K, V> Pair; struct Node { Pair p; bool bIsExist; }; enum {NOEXIST = 0,EXISTS}; bool Insert(const Pair &p) { CheckCapacity(); size_t nIndex = _HashFunc(p.first, m_Table.size()); while (m_Table[nIndex].bIsExist == EXISTS) { if (m_Table[nIndex].p.first == p.first) { return false; } nIndex++; if (nIndex == m_Table.size()) { nIndex = 0; } } m_Table[nIndex].p = p; m_Table[nIndex].bIsExist = EXISTS; m_nNumber++; return true; } V Find(const K&& key) { size_t nIndex = _HashFunc(key, m_Table.size()); if (m_Table[nIndex].p.first == key) { return m_Table[nIndex].p.second; } return 0; } private: // 檢查負載因子,並用素數表初始化容器大小 void CheckCapacity() { if (0 == m_Table.size()) { m_Table.resize(GetNextPrime(0)); } else if (m_nNumber * 10 / m_Table.size() > 7) { vector <Node> NewVect; NewVect.resize(GetNextPrime(m_Table.size())); NewVect.assign(m_Table.begin(), m_Table.end()); m_Table.swap(NewVect); } } size_t _HashFunc(const K& key, const size_t& size) { HashFunc hash; return hash(key) % size; } private: vector <Node> m_Table; int m_nNumber; // 已存node數量 };
#include "HashTab.h" void main() { HashTab<int, int> hash; hash.Insert(pair<int,int>(5, 55)); hash.Insert(pair<int,int>(6, 66)); hash.Insert(pair<int,int>(7, 77)); cout << hash.Find(5) << endl; cout << hash.Find(6) << endl; cout << hash.Find(7) << endl; HashTab<string, string> hash1; hash1.Insert(pair<string, string>("a", "hello")); hash1.Insert(pair<string, string>("b", "word")); cout << hash1.Find("a") << endl; cout << hash1.Find("b") << endl; }