其中,m為雜湊表長。由此可知,雙重雜湊法探測下一個開放地址的公式為:
(h1(key)
+ i * h2(key)) % m (1≤i≤m-1)
定義h2的方法較多,但無採用什麼方法都必須使h2(key)的值和m互素(又稱互質,表示兩數的最大公約數為1,或者說是兩數沒有共同的因子,1除外)才能使發生衝突的同義詞地址均勻地分佈在整個雜湊表中,否則可能造成同義詞地址的迴圈計算。若m為素數,則h2取1至m-1之間的任何數均與m互素,因此可以簡單地將h2定義為:
h2(key)
= key % (m - 2) + 1
8.4
剖析System.Collections.Hashtable
萬物之母object類中定義了一個GetHashCode()方法,這個方法預設的實現是返回一個唯一的整數值以保證在object的生命期中不被修改。既然每種型別都是直接或間接從object派生的,因此所有物件都可以訪問該方法。自然,字串或其他型別都能以唯一的數字值來表示。也就是說,GetHashCode()方法使得所有物件的雜湊函式構造方法都趨於統一。當然,由於GetHashCode()方法是一個虛方法,你也可以通過重寫這個方法來構造自己的雜湊函式。
8.4.1
Hashtable的實現原理
Hashtable使用了閉雜湊法來解決衝突,它通過一個結構體bucket來表示雜湊表中的單個元素,這個結構體中有三個成員:
(1)
key :表示鍵,即雜湊表中的關鍵字。
(2)
val :表示值,即跟關鍵字所對應值。
(3)
hash_coll :它是一個int型別,用於表示鍵所對應的雜湊碼。
int型別佔據32個位的儲存空間,它的最高位是符號位,為“0”時,表示這是一個正整數;為“1”時表示負整數。hash_coll使用最高位表示當前位置是否發生衝突,為“0”時,也就是為正數時,表示未發生衝突;為“1”時,表示當前位置存在衝突。之所以專門使用一個位用於存放雜湊碼並標註是否發生衝突,主要是為了提高雜湊表的執行效率。關於這一點,稍後會提到。
Hashtable解決衝突使用了雙重雜湊法,但又跟前面所講的雙重雜湊法稍有不同。它探測地址的方法如下:
h(key,
i) = h1(key) + i * h2(key)
其中雜湊函式h1和h2的公式如下:
h1(key)
= key.GetHashCode()
h2(key)
= 1 + (((h1(key) >> 5) + 1) % (hashsize - 1))
由於使用了二度雜湊,最終的h(key,
i)的值有可能會大於hashsize,所以需要對h(key, i)進行模運算,最終計算的雜湊地址為:
雜湊地址
= h(key, i) % hashsize
【注意】:bucket結構體的hash_coll欄位所儲存的是h(key,
i)的值而不是雜湊地址。
雜湊表的所有元素存放於一個名稱為buckets(又稱為資料桶)
的bucket陣列之中,下面演示一個雜湊表的資料的插入和刪除過程,其中資料元素使用(鍵,值,雜湊碼)來表示。注意,本例假設Hashtable的長度為11,即hashsize
= 11,這裡只顯示其中的前5個元素。
(1)插入元素(k1,v1,1)和(k2,v2,2)。
由於插入的兩個元素不存在衝突,所以直接使用h1(key)
% hashsize的值做為其雜湊碼而忽略了h2(key)。其效果如圖8.6所示。
(2)插入元素(k3,v3,12)
新插入的元素的雜湊碼為12,由於雜湊表長為11,12 % 11 =
1,所以新元素應該插入到索引1處,但由於索引1處已經被k1佔據,所以需要使用h2(key)重新計算雜湊碼。
h2(key)
= 1 + (((h1(key) >> 5) + 1) % (hashsize - 1))
h2(key)
= 1 + ((12 >> 5) + 1) % (11 - 1)) = 2