← 回首頁

雜湊(Hash Table 分離鏈接法 / Bloom Filter 布隆過濾器)

想在一大堆資料裡找某一筆,一個個翻太慢。雜湊的點子是用一條固定公式把「鑰匙」直接算成一個格子編號, 存進去和找回來都一步到位——像行李按護照號碼分進對應的置物櫃,不必一格格找。這頁比較兩種用法: 雜湊表(Hash Table)把完整資料放進格子,能準確答出某個鍵對應什麼值;布隆過濾器(Bloom Filter)更省, 連東西本體都不存、只在牆上打幾個記號,用極少空間回答「一定沒有」或「可能有」——寧可偶爾誤報也絕不漏報。 下面你可以一步步看鑰匙怎麼被算進格子,以及碰撞與偽陽性是怎麼發生的。

兩者都靠雜湊函數把任意鍵映射到固定大小的容器,但回答的問題不同:雜湊表是精確型集合,儲存完整鍵值, 以鏈結串列處理碰撞,能準確回答「這個鍵對應的值是什麼」;布隆過濾器是機率型集合,只存 0/1 位元、不存鍵值本身, 以極省空間換來「可能存在/一定不存在」的機率性回答,且必須承擔偽陽性(誤判存在)的風險,但絕無偽陰性。

1. 雜湊表(Hash Table,分離鏈接法)

以「桶陣列 + 鏈結串列」處理碰撞:getIndex(key) = |key.hashCode()| % capacity(本例為整數鍵,Java Integer.hashCode() 即值本身,故 index = key % 8);put 先走鏈比對是否已有相同 key(有則更新 value),否則於鏈頭插入新節點; get/remove 同樣先走鏈比對,找不到則回傳 null(miss)。碰撞集中在同一桶時需走訪整條鏈,平均 O(1)、最差 O(n)。

固定示範腳本(capacity 8):put(3,30) put(11,110)[碰撞!] put(5,50) put(19,190)[三鏈!] put(11,111)[更新] get(11) get(4)[miss] remove(3)。終態(獨立 Map 模擬):{11:111, 5:50, 19:190};桶 3 鏈(頭插序):19→11;桶 5:5。

操作腳本

    鏈上比較次數:0
    怎麼看這些格子:每一「列」是一個置物櫃(桶,編號 0-7);同一列往右的格子,是被算到同一個櫃子、串在後面的數字(最左邊是最近放進去的,越往右越舊)。空白格代表那裡還沒有東西。 會有「很多格子」是因為每個櫃子後面可能串好幾個數字——列數固定 8 個櫃子,欄數則看某個櫃子最多串了幾個。 藍色外框=程式正在比對的格,粉紅色=這一步剛命中/剛插入/剛刪除的格。

    雜湊表面板

    size0
    loadFactor0.00

    已執行操作紀錄

    2. 布隆過濾器(Bloom Filter,雙重雜湊置位)

    以 m 個位元 + k 個雜湊函數運作:add(item) 對每個 i=0..k-1 計算 index = getHash(item,i),把 bits[index] 設為 1; mightContain(item) 同樣計算 k 個 index,只要遇到某位元為 0 就能提早判定「一定不存在」,k 個位元皆為 1 才回傳「可能存在」。 本頁 k 個雜湊函數以雙重雜湊法模擬:hash_i = hash1 + i·hash2(hash1 = Java String.hashCode()、hash2 = FNV-1a, 皆以 32-bit 溢位語意運算),因此不同字串仍可能被映射到完全相同的 k 個位元位置 —— 這正是偽陽性的成因。

    固定示範腳本(m=16 bits,k=3):add('cat') add('dog') → query('cat')[真陽性] query('cat2')[偽陽性!] query('bird')[真陰性,早斷]。置位集合(實測):{2,4,6,11,12,13}。

    操作腳本

      位元操作次數:0
      單列 16 格代表位元陣列(0/1);藍色外框(active)為本步驟計算出的雜湊位置,粉紅(pivot)標示正在設定/檢查的格。

      查詢結果

      (尚無查詢結果)

      已加入元素

      雜湊計算明細

      已執行操作紀錄

      偽陽性判定誠實標示:查詢字串的 k 個位元恰好全部被其他已加入元素置過位時,就會誤判為「可能存在」。 以本例的 'cat2' 為例,它的偽陽性來自雜湊碰撞 —— 'cat2' 算出的 3 個位置 [12,11,6] 剛好都落在 'cat'/'dog' 已置位的集合內,與 'cat2' 字面上跟 'cat' 相似完全無關(純粹是雜湊值巧合碰撞;若改用一個字面上毫不相關、 但雜湊值恰好落入同一組位置的字串,一樣會被誤判為偽陽性)。