散列表是根據鍵而直接訪問在記憶體儲存位置的資料結構。也就是說，它透過計算出一個鍵值的函式，將所需查詢的資料對映到表中一個位置來讓人訪問，這加快了查詢速度。這個對映函式稱做雜湊函式，存放記錄的陣列稱做散列表。 一個通俗的例子是，為了查詢電話簿中某人的號碼，可以建立一個按照人名首字母順序排列的表（即建立人名 x 到首字母 F x 的一個函式關係），在首字母為 W 的表中查詢 王 姓的電話號碼，顯然比直接查詢就要快得多。這裡使用人名作為關鍵字，取首字母 是這個例子中雜湊函式的函式法則 F ，存放首字母的表對應散列表。關鍵字和函式法則理論上可以任意確定。 可以將散列表理解為一串按順序放的陣列，陣列的下標是從 key 經過計算得出，陣列每個位置存放 value。這裡有很多將 key 轉換為下標的函式，比如取模，md5 等。可以在雜湊表視覺化頁面 直觀操作，理解這裡的資料結構。 基本概念 若關鍵字為 k，則其值存放在 f k 的儲存位置上。由此，不需比較便可直接取得所查記錄。稱這個對應關係 f 為雜湊函式，按這個思想建立的表為散列表。 對不同的關鍵字可能得到同一雜湊地址，即 k_1 neq k_2，而 f k_1f k_2，這種現象稱為衝突。具有相同函式值的關鍵字對該雜湊函式來說稱做同義詞。綜上所述，根據雜湊函式 f k 和處理衝突的方法將一組關鍵字對映到一個有限的連續的地址集（區間）上，並以關鍵字在地址集中的 像 作為記錄在表中的儲存位置，這種表便稱為散列表，這一對映過程稱為雜湊造表或雜湊，所得的儲存位置稱雜湊地址。 若對於關鍵字集合中的任一個關鍵字，經雜湊函式映象到地址集合中任何一個地址的機率是相等的，則稱此類雜湊函式為均勻雜湊函式（Uniform Hash function），這就使關鍵字經過雜湊函式得到一個 隨機的地址，從而減少衝突。 構造雜湊函式 雜湊函式能使對一個數據序列的訪問過程更加迅速有效，透過雜湊函式，資料元素將被更快定位。 直接定址法：取關鍵字或關鍵字的某個線性函式值為雜湊地址。即 hash kk 或 hash kacdot k b，其中 ab 為常數（這種雜湊函式叫做自身函式） 數字分析法：假設關鍵字是以 r 為基的數，並且雜湊表中可能出現的關鍵字都是事先知道的，則可取關鍵字的若干數位組成雜湊地址。 平方取中法：取關鍵字平方後的中間幾位為雜湊地址。通常在選定雜湊函式時不一定能知道關鍵字的全部情況，取其中的哪幾位也不一定合適，而一個數平方後的中間幾位數和數的每一位都相關，由此使隨機分佈的關鍵字得到的雜湊地址也是隨機的。取的位數由表長決定。 摺疊法：將關鍵字分割成位數相同的幾部分（最後一部分的位數可以不同），然後取這幾部分的疊加和（捨去進位）作為雜湊地址。 隨機數法 除留餘數法：取關鍵字被某個不大於散列表表長 m 的數 p 除後所得的餘數為雜湊地址。即 hash kk bmod p ple m。不僅可以對關鍵字直接取模，也可在摺疊法、平方取中法等運算之後取模。對 p 的選擇很重要，一般取素數或 m，若 p 選擇不好，容易產生衝突。 處理衝突 為了知道衝突產生的相同雜湊函式地址所對應的關鍵字，必須選用另外的雜湊函式，或者對沖突結果進行處理。而不發生衝突的可能性是非常之小的，所以通常對沖突進行處理。常用方法有以下幾種： 開放定址法（open addressing）：hash_ihash keyd_i bmod m i12kk le m1，其中 hash key 為雜湊函式，m 為散列表長，d_i 為增量序列，i 為已發生衝突的次數。增量序列可有下列取法： d_i123m1 稱為線性探測；即 d_ii ，或者為其他線性函式。相當於逐個探測存放地址的表，直到查詢到一個空單元，把雜湊地址存放在該空單元。 d_ipm 12 pm 22pm 32pm k2 k le m2 稱為 平方探測 Quadratic Probing。相對線性探測，相當於發生衝突時探測間隔 d_ii2 個單元的位置是否為空，如果為空，將地址存放進去。 d_i 偽隨機數序列，稱為 偽隨機探測。 顯示線性探測填裝一個散列表的過程： 關鍵字為 8918495869 插入到一個散列表中的情況。此時線性探測的方法是取 d_ii。並假定取關鍵字除以 10 的餘數為雜湊函式法則。 雜湊地址 空表 插入 89 插入 18 插入 49 插入 58 插入 69 0 49 49 49 1 58 58 2 69 3 4 5 6 7 8 18 18 18 18 9 89 89 89 89 89 第一次衝突發生在填裝 49 的時候。地址為 9 的單元已經填裝了 89 這個關鍵字，所以取 i1，往下查詢一個單位，發現為空，所以將 49 填裝在地址為 0 的空單元。第二次衝突則發生在 58 上，取 i3，往下查詢 3 個單位，將 58 填裝在地址為 1 的空單元。69 同理。 表的大小選取至關重要，此處選取 10 作為大小，發生衝突的機率就比選擇質數 11 作為大小的可能性大。越是質數，mod 取餘就越可能均勻分佈在表的各處。 聚集（Cluster，也翻譯做 堆積）的意思是，在函式地址的表中，雜湊函式的結果不均勻地佔據表的單元，形成區塊，造成線性探測產生一次聚集（primary clustering）和平方探測的二次聚集（secondary clustering），雜湊到區塊中的任何關鍵字需要查詢多次試選單元才能插入表中，解決衝突，造成時間浪費。對於開放定址法，聚集會造成效能的災難性損失，是必須避免的。 單獨連結串列法：將雜湊到同一個儲存位置的所有元素儲存在一個連結串列中。實現時，一種策略是散列表同一位置的所有衝突結果都是用棧存放的，新元素被插入到表的前端還是後端完全取決於怎樣方便。 雙雜湊。 再雜湊：hash_i hash_i key i12k。hash_i 是一些雜湊函式。即在上次雜湊計算發生衝突時，利用該次衝突的雜湊函式地址產生新的雜湊函式地址，直到衝突不再發生。這種方法不易產生 聚集（Cluster），但增加了計算時間。 建立一個公共溢位區。 例程 在 C 語言中，實現以上過程的簡要程式： 開放定址法： HashTable InitializeTable int TableSize HashTable H int i 為散列表分配空間 有些編譯器不支援為 struct HashTable 分配空間，聲稱這是一個不完全的結構， 可使用一個指向 HashTable 的指標為之分配空間。 如：sizeof Probe，Probe 作為 HashTable 在 typedef 定義的指標。 H malloc sizeof struct HashTable 散列表大小為一個質數 HTableSize Prime 分配表所有地址的空間 HCells malloc sizeof Cell HTableSize 地址初始為空 for i 0 i HTableSize i HCells iinfo Empty return H 查詢空單元並插入： Position Find ElementType Key HashTable H Position Current int CollisionNum 衝突次數初始為 0 透過表的大小對關鍵字進行處理 CollisionNum 0 Current Hash Key HTableSize 不為空時進行查詢 while HCells Currentinfo Empty HCells CurrentElement Key Current CollosionNum CollisionNum 向下查詢超過表範圍時回到表的開頭 if Current HTableSize Current HTableSize return Current 分離連線法 查詢效率 散列表的查詢過程基本上和造表過程相同。一些關鍵碼可透過雜湊函式轉換的地址直接找到，另一些關鍵碼在雜湊函式得到的地址上產生了衝突，需要按處理衝突的方法進行查詢。在介紹的三種處理衝突的方法中，產生衝突後的查詢仍然是給定值與關鍵碼進行比較的過程。所以，對散列表查詢效率的量度，依然用平均查詢長度來衡量。 查詢過程中，關鍵碼的比較次數，取決於產生衝突的多少，產生的衝突少，查詢效率就高，產生的衝突多，查詢效率就低。因此，影響產生衝突多少的因素，也就是影響查詢效率的因素。影響產生衝突多少有以下三個因素： 雜湊函式是否均勻； 處理衝突的方法； 散列表的載荷因子。 載荷因子 散列表的載荷因子定義為：alpha 填入表中的元素個數 散列表的長度 alpha 是散列表裝滿程度的標誌因子。由於表長是定值，alpha 與 填入表中的元素個數 成正比，所以，alpha 越大，表明填入表中的元素越多，產生衝突的可能性就越大；反之，alpha 越小，標明填入表中的元素越少，產生衝突的可能性就越小。實際上，散列表的平均查詢長度是載荷因子 alpha 的函式，只是不同處理衝突的方法有不同的函式。 對於開放定址法，荷載因子是特別重要因素，應嚴格限制在 0708 以下。超過 08，查表時的 CPU 快取不命中（cache missing）按照指數曲線上升。因此，一些採用開放定址法的 hash 庫，如 Java 的系統庫限制了荷載因子為 075，超過此值將 resize 散列表。 舉例：Linux 核心的 bcache Linux 作業系統在物理檔案系統與塊裝置驅動程式之間引入了 緩衝區快取（Buffer Cache，簡稱 bcache）。當讀寫磁碟檔案的資料，實際上都是對 bcache 操作，這大大提高了讀寫資料的速度。如果要讀寫的磁碟資料不在 bcache 中，即快取不命中（miss），則把相應資料從磁碟載入到 bcache 中。一個快取資料大小是與檔案系統上一個邏輯塊的大小相對應的（例如 1KiB 位元組），在 bcache 中每個快取資料塊用 struct buffer_head 記載其元資訊： struct buffer_head char b_data 指向快取的資料塊的指標 unsigned long b_blocknr 邏輯塊號 unsigned short b_dev 裝置號 unsigned char b_uptodate 快取中的資料是否是最新的 unsigned char b_dirt 快取中資料是否為髒資料 unsigned char b_count 這個快取塊被引用的次數 unsigned char b_lock b_lock 表示這個快取塊是否被加鎖 struct task_struct b_wait 等待在這個快取塊上的程序 struct buffer_head b_prev 指向快取中相同 hash 值的下一個快取塊 struct buffer_head b_next 指向快取中相同 hash 值的上一個快取塊 struct buffer_head b_prev_free 快取塊空閒連結串列中指向下一個快取塊 struct buffer_head b_next_free 快取塊空閒連結串列中指向上一個快取塊 整個 bcache 以 struct buffer_head 為基本資料單元，組織為一個封閉定址（close addressing，即 單獨連結串列法 解決衝突）的散列表 struct buffer_head hash_table NR_HASH 雜湊函式的輸入關鍵字是 b_blocknr（邏輯塊號）與 b_dev（裝置號）。計算 hash 值的雜湊函式表示式為： b_dev b_blocknr NR_HASH 其中 NR_HASH 是散列表的條目總數。發生 衝突 的 struct buffer_head，以 b_prev 與 b_next 指標組成一個雙向（不迴圈）連結串列。bcache 中所有的 struct buffer_head，包括使用中不空閒與未使用空閒的 struct buffer_head，以 b_prev_free 和 b_next_free 指標組成一個雙向迴圈連結串列 free_list，其中未使用空閒的 struct buffer_head 放在該連結串列的前部。 參考文獻 H Category 搜尋演演算法