hash 哈希查找復雜度為什么這么低?
(2017-06-23 21:20:36)
from:
還有:http://blog.csdn.net/wendavidoi/article/details/50670016
http://www.cnblogs.com/dolphin0520/archive/2012/09/28/2700000.html
哈希算法,又稱散列算法,能大大提高搜索的效率。它的主要工作是將一個數字映射到一個表格的某個地方打一個比喻,哈希就像那些公司前台的接待人員,直接將領導的電話記住。而哈希,就是將每一個元素的位置記住,就是我們不去找某個東西,而是將它的位置算出來。
1)hash它為什么對於鍵-值查找性能高
學過數據結構的,都應該曉得,線性表和樹中,記錄在結構中的相對位置是隨機的,記錄和關鍵字之間不存在明確的關系,因此在查找記錄的時候,需要進行一系列的關鍵字比較,這種查找方式建立在比較的基礎之上,在.net中(Array,ArrayList,List)這些集合結構采用了上面的存儲方式。
比如,現在我們有一個班同學的數據,包括姓名,性別,年齡,學號等。假如數據有
姓名 |
性別 |
年齡 |
學號 |
張三 |
男 |
15 |
1 |
李四 |
女 |
14 |
2 |
王五 |
男 |
14 |
3 |
假如,我們按照姓名來查找,假設查找函數FindByName(string name);
1)查找“張三”
只需在第一行匹配一次。
2)查找"王五"
在第一行匹配,失敗,
在第二行匹配,失敗,
在第三行匹配,成功
上面兩種情況,分別分析了最好的情況,和最壞的情況,那么平均查找次數應該為 (1+3)/2=2次,即平均查找次數為(記錄總數+1)的1/2。
盡管有一些優化的算法,可以使查找排序效率增高,但是復雜度會保持在log2n的范圍之內。
如何更更快的進行查找呢?我們所期望的效果是一下子就定位到要找記錄的位置之上,這時候時間復雜度為1,查找最快。如果我們事先為每條記錄編一個序號,然后讓他們按號入位,我們又知道按照什么規則對這些記錄進行編號的話,如果我們再次查找某個記錄的時候,只需要先通過規則計算出該記錄的編號,然后根據編號,在記錄的線性隊列中,就可以輕易的找到記錄了 。
注意,上述的描述包含了兩個概念,一個是用於對學生進行編號的規則,在數據結構中,稱之為哈希函數,另外一個是按照規則為學生排列的順序結構,稱之為哈希表。
仍以上面的學生為例,假設學號就是規則,老師手上有一個規則表,在排座位的時候也按照這個規則來排序,查找李四,首先該教師會根據規則判斷出,李四的編號為2,就是在座位中的2號位置,直接走過去,“李四,哈哈,你小子,就是在這!”
看看大體流程:

從上面的圖中,可以看出哈希表可以描述為兩個筒子,一個筒子用來裝記錄的位置編號,另外一個筒子用來裝記錄,另外存在一套規則,用來表述記錄與編號之間的聯系。這個規則通常是如何制定的呢?
直接取值法
直接取值法,就是直接以當前元素的值來決定它的位置。化成函數就是 H(x)=x。這種方法的好處是不可能沖突,除非兩個元素一模一樣。而且這樣甚至能夠保證在哈希表里面的元素有序,就像計數排序一樣。
但是這種方法也有缺點,當x的取值太大的時候,耗費的空間同時也會很大。舉個例子,如果有3個數:3,6814246421,1654654614874213,那光是這三個數,就已經耗費了巨大的內存空間了。
除法哈希
既然直接取值會耗費很大的內存空間,那我們可以模一下這個變量,一般來說,模一個數組長度,就是不錯的選擇。這樣既可以剛剛好放下這些數據,又不會耗費太多的空間。化成函數就是H(x)=x%m。但是這樣就會出現沖突。所謂沖突,就是指兩個取值不一樣的數,它們在哈希后得出的值相同,映射到了同一個位置。也就是說,a!=b,但H(a)==H(b)。在這里我們先不討論沖突。那怎樣盡量避免沖突呢?答案就是:模一個素數!可以證明,當H(x)定義中x%m的m的因數越多,則沖突的概率就越大。不過,其實最好的方法還是增大表格的大小,這樣相應的,x%m的取值也會更為多樣化。
位運算哈希
除法哈希的缺點之一,是容易沖突,而且有的時候甚至還不與整一個數相關。下面我就介紹一種位運算哈希,這種哈希主要運用乘法,而且多是位運算,速度較快。同時,除法哈希要求數組長度最好是一個素數,但在計算機中,我們更喜歡讓數組長度為2的冪數,這樣就不會浪費空間。確切地說,就是利用位運算,充分的混合元素。舉個例子,ELFHash就是一個很好的實現。
乘法哈希
最后介紹一種最實用且最容易記的哈希算法。這種哈希函數叫做乘法哈希。其原理就是將原數看做一個n進制的數在轉換回十進制。這種哈希算法的典型實現有BKDRHash。理解起來很容易,也是奧賽中經常用到的算法,一般來說沖突率非常小。
順帶附上BKDRHash的核心代碼(已過測試):
unsigned int BKDRHash(char *key){
unsigned int seed=131;
unsigned int hash=0;
while(*key)
{
hash = hash * seed + (*key++);
}
return hash%MOD;
}
乘法哈希較常用到