什麼是散列表(Hash Table)

散列表(Hash table,也叫哈希表),是根據鍵(Key)而直接訪問在內存存儲位置的數據結構。也就是說,它經過計算一個關於鍵值的函數,將所需查詢的數據映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱作散列函數,存放記錄的數組稱作散列表。數組

一個通俗的例子是,爲了查找電話簿中某人的號碼,能夠建立一個按照人名首字母順序排列的表(即創建人名 x 到首字母 F(x) 的一個函數關係),在首字母爲W的表中查找「王」姓的電話號碼,顯然比直接查找就要快得多。這裏使用人名做爲關鍵字,「取首字母」是這個例子中散列函數的函數法則F(),存放首字母的表對應散列表。關鍵字和函數法則理論上能夠任意肯定。數據結構

1.基本概念

  • 若關鍵字爲 k,則其值存放在f(k) 的存儲位置上。由此,不需比較即可直接取得所查記錄。稱這個對應關係 f 爲散列函數,按這個思想創建的表爲散列表。
  • 對不一樣的關鍵字k可能獲得同一散列地址,即

$$ k1≠k2 $$函數

,而性能

$$ f(k1)=f(k2) $$spa

,這種現象稱爲衝突(或碰撞,英語:Collision)。具備相同函數值的關鍵字對該散列函數來講稱作同義詞。綜上所述,根據散列函數f(k) 和處理衝突的方法將一組關鍵字映射到一個有限的連續的地址集(區間)上,並以關鍵字在地址集中的「像」做爲記錄在表中的存儲位置,這種表便稱爲散列表,這一映射過程稱爲散列造表或散列,所得的存儲位置稱散列地址。翻譯

  • 若對於關鍵字集合中的任一個關鍵字,經散列函數映象到地址集合中任何一個地址的機率是相等的,則稱此類散列函數爲均勻散列函數(Uniform Hash function),這就使關鍵字通過散列函數獲得一個「隨機的地址」,從而減小衝突。

2.構造散列函數的方法

散列函數能使對一個數據序列的訪問過程更加迅速有效,經過散列函數,數據元素將被更快定位。orm

  • 直接定址法:取關鍵字或關鍵字的某個線性函數值爲散列地址。即

$$ hash(k)=k $$遊戲

內存

$$ hash(k)=a \cdot k+b $$rem

, 其中ab爲常數(這種散列函數叫作自身函數)

  • 數字分析法:假設關鍵字是以r爲基的數,而且哈希表中可能出現的關鍵字都是事先知道的,則可取關鍵字的若干數位組成哈希地址。
  • 平方取中法:取關鍵字平方後的中間幾位爲哈希地址。一般在選定哈希函數時不必定能知道關鍵字的所有狀況,取其中的哪幾位也不必定合適,而一個數平方後的中間幾位數和數的每一位都相關,由此使隨機分佈的關鍵字獲得的哈希地址也是隨機的。取的位數由表長決定。
  • 摺疊法:將關鍵字分割成位數相同的幾部分(最後一部分的位數能夠不一樣),而後取這幾部分的疊加和(捨去進位)做爲哈希地址。
  • 除留餘數法:取關鍵字被某個不大於散列表表長m的數p除後所得的餘數爲散列地址。即

$$ hash(k)=k\,{\bmod {\,}}p $$

$$ p\leq m $$

不只能夠對關鍵字直接取模,也可在摺疊法、平方取中法等運算以後取模。對p的選擇很重要,通常取素數或m,若p選擇很差,容易產生衝突。

3.處理衝突

爲了知道衝突產生的相同散列函數地址所對應的關鍵字,必須選用另外的散列函數,或者對衝突結果進行處理,而不發生衝突的可能性是很是之小的,因此一般對衝突進行處理。經常使用方法有如下幾種:

開放尋址法(open addressing)。想象一下,有一趟對號入座的火車,假設它只有一節車箱,上來一位坐7號座位的旅客。過了一下子,又上來一位旅客,他買到的是一張假票,也是7號座位,這時怎麼辦呢?列車長想了想,讓拿假票的旅客去坐8號座位。過了一下子,應該坐8號座位的旅客上來了,列車長對他說8號座位已經有人了,你去坐9號座位吧。哦?9號早就有人了?10號也有人了?那你去坐11號吧。能夠想見,越到後來,當空座愈來愈少時,碰撞的概率就越大,尋找空座愈發地費勁。可是,若是是火車的上座率只有50%或者更少的狀況呢?也許真正坐8號座位的乘客永遠不會上車,那麼讓拿假票的乘客坐8號座位就是一個很好的策略了。因此,這是一個空間換時間的遊戲。玩好這個遊戲的關鍵是,讓旅客分散地坐在車箱裏。如何才能作到這一點呢?答案是,對於每位不一樣的旅客使用不一樣的探查序列。例如,對於旅客 A,探查座位 7,8,23,56……直到找到一個空位;對於旅客B,探查座位 25,66,77,1,3……直到找到一個空位。若是有 m 個座位,每位旅客可使用 <0, 1, 2, ..., m-1> 的m! 個排列中的一個。

顯而易見,最好減小兩個旅客使用相同的探查序列的狀況。也就是說,但願把每位旅客儘可能分散地映射到 m! 種探查序列上。換句話說,理想狀態下,若是可以讓每一個上車的旅客,使用 m! 個探查序列中的任意一個的可能性是相同的,咱們就說實現了一致散列。(這裏沒有用「隨機」這個詞兒,由於實際是不可能隨機取一個探查序列的,由於在查找這名旅客時還要使用相同的探查序列)。

線性探查:最簡單的方法是,若是發現 values[8] 已經被佔用了,就看看 values[9] 是否空着,若是 values[9] 也被佔用了,就看看 values[0] 是否是還空着。完整的描述是,先使用 H() 函數獲取 k 的第一個地址,若是這個地址已被佔用,就探查下一個緊挨着的地址,若是仍是不能用,就探查下一個緊挨着的地址,若是到達了數組的末尾,就卷繞到數組的開頭,若是探查了 m 次仍是沒有找到空槽,就說明數組已經滿了,這就是線性探查(linear probing)

真正的一致散列是難以實現的,實踐中,經常採用它的一些近似方法。經常使用的產生探查序列的方法有:線性探查,平方探測,以及雙重散列探查。這些方法都不能實現一致散列,由於它們能產生的不一樣探查序列數都不超過

$$ m^2 $$

個(一致散列要求有 m! 個探查序列)。在這三種方法中,雙重散列能產生的探查序列數最多,於是能給出最好的結果。

顯示線性探測填裝一個散列表的過程:

關鍵字爲{89,18,49,58,69}插入到一個散列表中的狀況。此時線性探測的方法是取

$$ d_{i}=i $$

並假定取關鍵字除以10的餘數爲散列函數法則。


第一次衝突發生在填裝49的時候。地址爲9的單元已經填裝了89這個關鍵字,因此取 $i=1$,往下查找一個單位,發現爲空,因此將49填裝在地址爲0的空單元。

第二次衝突則發生在58上,取i=3,往下查找3個單位,將58填裝在地址爲1的空單元。69同理。
表的大小選取相當重要,此處選取10做爲大小,發生衝突的概率就比選擇質數11做爲大小的可能性大。越是質數,mod取餘就越可能均勻分佈在表的各處。

彙集(Cluster,也翻譯作「堆積」)的意思是,在函數地址的表中,散列函數的結果不均勻地佔據表的單元,造成區塊,形成線性探測產生一次彙集(primary clustering)和平方探測的二次彙集(secondary clustering),散列到區塊中的任何關鍵字須要查找屢次試選單元才能插入表中,解決衝突,形成時間浪費。對於開放定址法,彙集會形成性能的災難性損失,是必須避免的。

轉載請註明出處
相關文章
相關標籤/搜索