雜湊表計算工具

雜湊表是以雜湊函數將鍵對應到表中的位置,使搜尋只需大致固定時間的資料結構。本工具以表格顯示依序放入鍵時,在哪個位置發生碰撞、最後儲存在哪裡。切換碰撞的處理方法,即可確認搜尋所需的工作量如何變化。

鍵與表

最多 60 個 0 以上的整數或 8 個字元以內的字串。整數使用 h(k) = k mod m,字串使用字元碼總和除以 m 的餘數。相同的鍵只放入 1 次。

結果

負載因子(鍵的數量 ÷ 表的大小)
–
發生碰撞的鍵數
–
儲存前的平均探測次數
–

各鍵的儲存過程

碰撞處理方法的比較

探測次數是儲存(或找到)鍵之前所檢查的位置數。鏈結法則計算鍵位於該位置串列中的第幾個。二次探測法視表的大小與負載因子而定,即使有空位也可能找不到。

雜湊表與碰撞

雜湊表是以雜湊函數從鍵計算出表中的位置,並把資料放在該位置的資料結構。若鍵分散得好,不論鍵有多少個,都能以大致固定的時間找到。但不同的鍵可能對應到同一個位置,這稱為碰撞。以 h(k) = k mod 7 將 10、17、24、3 放入大小 7 的表,4 個鍵全都對應到位置 3 而發生碰撞。碰撞無法避免,因此如何處理會左右效能。

鏈結法與開放定址法

鏈結法是將對應到同一位置的鍵以鏈結串列串起來的方法。表不會滿溢,刪除也很簡單,但追蹤串列需要額外的搜尋時間,而且會存取記憶體中分散的位置。開放定址法是在碰撞時尋找表中其他空位並放入的方法,依移動方式分為線性探測法(每次 1 格)、二次探測法(1、4、9 … 格)與雙重雜湊法(依各鍵決定的間隔)。超過表大小的鍵無法放入,刪除時也需要「已刪除」的標記。

負載因子與探測次數

鍵的數量除以表的大小所得的值稱為負載因子。負載因子越高,碰撞越多,尤其是開放定址法的探測次數會急遽增加。線性探測法容易形成已占用位置連續相接的區塊,撞上該區塊的鍵又會讓區塊更長,這稱為聚集。二次探測法與雙重雜湊法藉由改變移動方式來減少這種聚集。請比較範例「線性探測產生聚集的例子」與「以二次探測與雙重雜湊分散」的平均探測次數。實際的雜湊表在負載因子超過約 0.5~0.75 時,會擴大表並重新放入所有鍵(重新雜湊)。

雜湊函數的選擇

好的雜湊函數會將鍵平均分散到整個表。使用 k mod m 時,選擇質數作為 m,即使鍵有規律(全是偶數、全是 10 的倍數等),也不容易偏斜。若只是單純加總字串的字元碼,像範例中的 cat、act、tac 這種只是字母順序不同的鍵一定會碰撞,因此實際上會採取對每個字元乘上權重再加總等做法。雜湊表廣泛用於程式語言的字典(關聯陣列)、資料庫的雜湊索引、快取的查詢等各種地方。

練習演算法題目

關於雜湊表中碰撞(collision)的說明,下列何者正確?

  1. A能從雜湊值求出原本的鍵
  2. B不同的鍵求出相同的雜湊值(存放位置)
  3. C相同的鍵每次執行都求出不同的雜湊值
查看答案與解析

正確答案:B. 不同的鍵求出相同的雜湊值(存放位置)

雜湊表以雜湊函數從鍵計算存放位置,平均能以 O(1) 進行搜尋、插入與刪除。鍵的種類遠多於表的大小,因此不同的鍵落在相同位置(碰撞)無法避免。所以要用把相同位置的元素串成串列的鏈結法,或尋找其他空位置的開放定址法等來處理碰撞。相同的鍵若不一定求出相同的雜湊值就無法搜尋;能否從雜湊值得知原本的鍵,則是用於密碼學時的性質,與碰撞無關。

在大小為7(位置0~6)的雜湊表中,以雜湊函數 h(k) = k mod 7 依序存放鍵10、17、24、3。發生碰撞時,使用把位置逐一往後移(6的下一個是0)來尋找空位置的線性探測法。鍵3會存放在哪個位置?

  1. A位置3
  2. B位置4
  3. C位置6
查看答案與解析

正確答案:C. 位置6

4個鍵除以7的餘數都是3,因此都會先檢查位置3。10放入位置3;17因位置3已被占用而放入位置4;24因位置3、4已被占用而放入位置5。3因位置3、4、5都已被占用,所以存放在位置6。如此可見,線性探測法會使被占用的位置聚集在一起(叢集),越晚來的鍵需要檢查的次數越多。

比較處理雜湊表碰撞的鏈結法(以鏈結串列串接相同位置的元素)與開放定址法(在表中尋找其他空位置),下列說明何者正確?負載因子為存放的元素數除以表的大小所得的值。

  1. A鏈結法在負載因子超過1時也能存放,但開放定址法無法存放超過表大小的元素,且負載因子接近1時搜尋次數會急遽增加
  2. B開放定址法不會發生碰撞,因此無論負載因子為何,搜尋都只需1次
  3. C鏈結法無論負載因子多高,搜尋的工作量都不變
查看答案與解析

正確答案:A. 鏈結法在負載因子超過1時也能存放,但開放定址法無法存放超過表大小的元素,且負載因子接近1時搜尋次數會急遽增加

鏈結法把相同位置的元素串成串列,因此元素數超過表的大小也能存放,但串列的平均長度與負載因子成正比增加,搜尋的工作量也隨之增加。開放定址法只存放在表中,元素數最多只能到表的大小,負載因子接近1時尋找空位的次數會急遽增加。無論哪種方法,負載因子超過設定值(例如約0.5~0.75)時,通常都會放大表格並重新放入所有元素(重新雜湊)。開放定址法也會發生碰撞,是靠尋找空位置來處理。

在元素很多、負載因子維持在0.5左右的雜湊表中,使用線性探測的開放定址法往往比鏈結法(以鏈結串列串接的方法)快,最適當的原因為下列何者?

  1. A發生碰撞時下一個檢查的位置是相鄰的元素,多半位於同一條快取行或緊接的下一條,因此記憶體存取較少
  2. B開放定址法完全不會發生碰撞
  3. C鏈結法需要計算2次以上的雜湊函數
查看答案與解析

正確答案:A. 發生碰撞時下一個檢查的位置是相鄰的元素,多半位於同一條快取行或緊接的下一條,因此記憶體存取較少

在現今的 CPU 中,因快取未命中而讀取主記憶體的時間,比比較等計算長得多。線性探測在碰撞時檢查相鄰位置,因此多半在同一條快取行內就能完成,預取也能發揮效果。鏈結法中,串列的下一個元素常位於記憶體中遠處,每追蹤一次指標都容易發生快取未命中。即使時間複雜度的階相同,實際速度也會因記憶體存取方式而有很大差異。不過,線性探測在負載因子變高時,被占用的位置會聚集而急遽變慢,因此要把負載因子維持在較低的程度。

常見問題

鏈結法與開放定址法哪一種比較快?

負載因子低時,開放定址法(尤其是線性探測法)依序檢查表中連續的位置,容易命中快取,因此常常比較快。負載因子變高後,開放定址法的探測次數會急遽增加,此時鏈結法較為穩定。

為什麼表的大小要選質數?

因為使用 k mod m 時,若鍵與 m 有公因數,被使用的位置就會偏斜。例如 m = 10 而鍵全是偶數時,只會用到偶數位置。若 m 是質數,就不容易發生這種偏斜。已知二次探測法在 m 為質數且負載因子在 0.5 以下時,一定能找到空位。

雜湊表搜尋的時間複雜度是多少?

若鍵分散得好,平均為 O(1)(與資料數量無關,固定)。但在所有鍵都碰撞到同一位置的最差情況下,會變成 O(n)。

考試中會怎麼出題?

日本基本資訊技術士考試等資訊類證照考試,常給定雜湊函數與碰撞的處理方法,要求回答依序放入鍵時某個鍵的儲存位置。請選擇範例,將表中「檢查的位置」與自己的答案比較看看。

相關術語

雜湊表 鏈結串列 時間複雜度(大O符號)

相關計算工具

半導體、電子電路與資訊計算工具 →