散列表(Hash table,也叫哈希表),是根據鍵(Key)而直接訪問在內存存儲位置的數據結構。也就是說,它經過計算一個關於鍵值的函數,將所需查詢的數據映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱作散列函數,存放記錄的數組稱作散列表。數組
一個通俗的例子是,爲了查找電話簿中某人的號碼,能夠建立一個按照人名首字母順序排列的表(即創建人名 x 到首字母 F(x) 的一個函數關係),在首字母爲W的表中查找「王」姓的電話號碼,顯然比直接查找就要快得多。這裏使用人名做爲關鍵字,「取首字母」是這個例子中散列函數的函數法則F(),存放首字母的表對應散列表。關鍵字和函數法則理論上能夠任意肯定。數據結構
$$ k1≠k2 $$函數
,而性能
$$ f(k1)=f(k2) $$spa
,這種現象稱爲衝突(或碰撞,英語:Collision)。具備相同函數值的關鍵字對該散列函數來講稱作同義詞。綜上所述,根據散列函數f(k) 和處理衝突的方法將一組關鍵字映射到一個有限的連續的地址集(區間)上,並以關鍵字在地址集中的「像」做爲記錄在表中的存儲位置,這種表便稱爲散列表,這一映射過程稱爲散列造表或散列,所得的存儲位置稱散列地址。翻譯
散列函數能使對一個數據序列的訪問過程更加迅速有效,經過散列函數,數據元素將被更快定位。orm
$$ hash(k)=k $$遊戲
或內存
$$ hash(k)=a \cdot k+b $$rem
, 其中ab爲常數(這種散列函數叫作自身函數)
$$ hash(k)=k\,{\bmod {\,}}p $$
$$ p\leq m $$
不只能夠對關鍵字直接取模,也可在摺疊法、平方取中法等運算以後取模。對p的選擇很重要,通常取素數或m,若p選擇很差,容易產生衝突。
爲了知道衝突產生的相同散列函數地址所對應的關鍵字,必須選用另外的散列函數,或者對衝突結果進行處理,而不發生衝突的可能性是很是之小的,因此一般對衝突進行處理。經常使用方法有如下幾種:
開放尋址法(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),散列到區塊中的任何關鍵字須要查找屢次試選單元才能插入表中,解決衝突,形成時間浪費。對於開放定址法,彙集會形成性能的災難性損失,是必須避免的。
轉載請註明出處