散列函數之雙重散列算法解決沖突問題


1. 問題

問題同《簡單散列函數算法》,這個例子並不是特別恰當,當在於簡單,數字小,方便驗證,方便理解,特別是計算概率的部分。

設有10個非負整數,用不多於20個的儲存單元來存放,如何存放這10個數,使得搜索其中的某一個數時,在儲存單元中查找的次數最少?

問題類似於,有10個帶號碼的球,放到編號為{0, 1, 2, …, 19}共20個盒子中,每個盒子最多放一個,問如何放,使能夠用最少的次數打開盒子,知道任一個球所在的盒子編號?

 

2. 分析

散列函數之單散列算法解決沖突問題》中,我們提到用單散列算法來解決沖突,比簡單散列算法沖突的比率有所降低,但18%的沖突率,對於實際應用來說還是略偏高,《初等數論及其應用》中,說明是從另一個角度來說明該沖突率高的原因。

設 h0(k) ≡ k (mod m), k = 球號, m = 盒子數量

hj(k) ≡ h0(k) + j,0<= j < m,  hj(k) 表示發生 j 次沖突后,球所放入的盒子編號

∴ hj+1(k) ≡ h0(k) + (j + 1) ≡ hj(k) + 1

∴ 只要有一個hi(k1) ≡ hj(k2)

則所有的hi+n(k1) ≡ hj+n(k2) (n = {0, 1, 2, …})

用數學歸納法可以很容易的證明

也可以用同余算法如下證明:

hi+n(k1) ≡ h0(k2) + n

∵ hi(k1) ≡ hj(k2)

∴ hi+n(k1) ≡ hj(k2) + n ≡ hj+n(k2)

∴ 只要有一個球對應的盒子編號hi(k1)與另一個hj(k2)沖突,則會有無數對 hi(k1)+n 和 hj(k2)+n  沖突

如《散列函數之單散列算法解決沖突問題》測試的數據中,0和19沖突,則 0+1 = 1 和 19+1 = 20也是沖突的,類似, 2和21, 3和22等等都會沖突,也就是說,只要球號中有對應的連續數列,就特別容易產生沖突,導致該序列查找的次數會劇增,這個問題稱為”clustering”,書中《初等數論及其應用》中翻譯為堵塞,我覺得翻譯為聚集沖突更合適,這是因為簡單的加1不能使數字不能足夠分散所致。

這里用一組聚集沖突的數據演示一下:

numList = [0, 1, 2, 19, 20, 21, 18, 44, 17, 38, 77];

測試結果發現,所有的連續數列的球號都排在前8個序號的盒子中,沖突的序列都需要4次的查找,而38因為和連續數列的第一個數字同余沖突,導致的查找次數更是達到8次,如果散列函數處理的數據中有如此的序列,那么算法性能會急劇下降。

 

為了解決這個問題,既然有單散列算法,那么自然就想到雙散列算法。

 

3. 雙重散列算法

雙散列算法為了解決單散列算法中 +j 過於連續,不夠分散而來,同時能夠進一步降低沖突率。其為了進一步分散沖突的數字,對沖突的球的數字再一次進行散列

h0(k) ≡ k (mod m), k = 球號

m = 盒子的數量,m 取接近最大盒子數量的素數

散列函數1 : g(k) ≡ k + 1 (mod m -2)

散列函數2 : hj(k) ≡ h0(k) + j*g(k) (mod m),  hj(k) 表示發生 j 次沖突后,球所放入的盒子編號

注意g(k)的模變了,變為 (m-2),為什么要用這個,一會分析沖突要用

 

4.雙重散列生成示例

仍用《散列函數之單散列算法解決沖突問題》中的測試數據{0, 1, 2, 7, 9, 15, 19, 20, 77, 38}

m = 19, m-2 = 17

球號 0 1 2 7 9 15 19 20 77 38
模19余數 0 1 2 7 9 15 0 1 4 0

分配前6個球時沒有沖突,分配如下:

盒子編號 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
球號 0 1 2         7   9           15  

當分配到第7個球19時,h0(19) ≡ 0 (mod 19), 需要放到編號為0的盒子,此時產生第一次沖突計算h1(k)

g(k) ≡ g(19) ≡ 19 + 1 ≡ 3 (mod 17)

h1(19) ≡ h0(19) + 1*g(19) ≡ 0 + 1*3 ≡ 3 (mod 19)

所以19該放至編號為3的盒子,該盒子為空,不沖突,放入即可。

類似的,可以求到前9個球放入的盒子的位置,都只有一次沖突:

盒子編號 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
球號 0 1 2 19   20   7   9   77       15  

當分配最后一個球38時,h0(38) ≡ 0 (mod 19), 第一次沖突

g(38) ≡ 38 + 1 ≡ 5 (mod 17)

h1(38) ≡ h0(38) + 1*g(38) ≡ 5 (mod 19),發現扔和20號球沖突,進一步計算h2(k):

h2(38) ≡ h0(38) + 2*g(38) ≡ 10 (mod 19),無沖突

通過整個上述散列函數計算過程,最終的散列值經過再次散列與沖突的次數 j 相乘后,最終的分配都比較分散,間隔相對均勻,因此沖突數顯著減少

 

5. 雙重散列函數聚集沖突問題

若雙重散列函數要發生聚集沖突,則

若雙重散列函數hi(k1)與另一個hj(k2)沖突(k1 != k2),且有hi+1(k1) 和  hj+1(k2) 沖突時:

∵ hi(k1) ≡ hj(k2)

∴ hi(k1) ≡ h0(k1) + i*g(k1) ≡ hj(k2) ≡ h0(k2) + j*g(k2)

∴ h0(k1) + i*g(k1) ≡ h0(k2) + j*g(k2)                      ①

同理:

∵ hi+1(k1) ≡ hj+1(k2)

∴ h0(k1) +(i+1)*g(k1) ≡ h0(k2) + (j+1)*g(k2)          ②

根據同余算法,②減①得:

g(k1) ≡ g(k2)   (mod m)

∵ 0 < g(k) < m – 2

∴ g(k) 都為最小余數

∴ g(k1) = g(k2)

∴ k1 + 1  ≡ k2 + 1 (mod m-2)

k1  ≡ k2  (mod m-2)                                          ③

 

Rosen的《初等數論及其應用》第6版207頁給出了該證明,但為了證明雙重散列函數沒有單散列函數的”clustering”問題,也就是書中所翻譯堵塞問題(我覺得翻譯有誤,按照說明更合適的翻譯應該是集中或聚集問題,即一段有序的數列一旦有一個數和另一段有序數列沖突,則會2段序列中后續的數列都會沖突,導致查找步驟急劇增加),其接着說如果上述條件成立,則可簡化同余式①,得:

h0(k1)  ≡ h0(k2) (mod m)

即:k1  ≡ k2  (mod m)

從而得出,在 m(m-2)內只有唯一的一個解得結論,即不會有沖突

我嘗試證明這個省掉的步驟未果,但卻找到一個反例了,我給該書的作者Rosen寫了封郵件,但一直沒回我,如看帖的朋友能補齊該證明請告訴我。

反例:當m = 19, m-2 = 17時:

設k1 = 1, k2 = 18

∴ h0(k1) = 1, h0(k2) = 18 (mod 19)

k1  ≡ 1 ≡ k2  ≡ 18   (mod 17) , 滿足同余③

同余式①:

h0(k1) + i*g(k1) ≡ 1 + i     (mod m = 19)

h0(k2) + j*g(k2)  ≡ 18 + j                

易知:當 i  = 0, j = 2時,他們成立(1 ≡ 20)

但1  ≡ 18  (mod 19) 並不成立

 

但是如果加一個條件: i = j

∵ g(k1) = g(k2)

∴ i*g(k1) = j*g(k2

∴ h0(k1)  ≡ h0(k2) (mod m)

即:k1  ≡ k2  (mod m)

根據中國剩余定理,只同時滿足:

k1  ≡ k2  (mod m-2)

k1  ≡ k2  (mod m)

當m 和 m -2互素時,公式 x ≡ a (mod m-2), x ≡ a (mod m) 在 m(m-2)的范圍內有且只有一個唯一解:

即: k1 = k2   (k < m(m-2))

所以在m(m-2)范圍內不會有2個數k1, k2模m沖突的情況下,又有hj(k) ≡ h0(k) + j*g(k) (mod m)再次沖突

但是仍然會發生,k1, k2模m沖突,且hj(k)又和另一個k3沖突的情況。

 

6. 雙重散列函數沖突概率問題

為了簡單,我們仍然只考慮在h0(k)沖突時,h1(k) ≡ h0(k) + 1*g(k) (mod m)再次沖突的概率:

仍設最大的球號為 s:

1> 由第5部分的證明知,當 s < m(m-2))時,這種沖突概率為0

2> 當 s > m(m-2)時,設k為第一個球的球號

根據中國剩余定理知,其他解 kx ≡ k (mod m(m-2)),會滿足:

kx  ≡ k  (mod m-2)

kx  ≡ k  (mod m)

即會產生h0(k)和h1(k)的同時沖突,其概率為每m(m-2)個數內會出現一次

故其沖突概率 = 1/m(m-2)

當m = 19時,沖突概率 = 0.31%,這個概率明顯要比簡單散列函數和單散列函數要低得多,所以雙重散列函數的沖突率是非常低的

 

7. python code 和演示結果

 1 m = 19;         #m -2 = 17, is a prime number
 2 
 3 #g(k) = (k + 1) % (m - 2)
 4 #h(k) = k % m
 5 #hj(k) = h(k) + j*g(k)
 6 def DoubleHash(numList):
 7     if len(numList) > m:
 8         print "num list len is :", len(numList), "is big than mod :", m;
 9         return None;
10 
11     hashList = [None] * m;
12     for k in numList:
13         for j in range(m):
14             g_k = (k + 1) % (m - 2);
15             hj_k = (k + j * g_k) % m;
16             if None == hashList[hj_k]:
17                 hashList[hj_k] = k;
18                 break;
19         else:
20             print "num list is too big, hash list is overflow";
21             return None;
22 
23     return hashList;
24     
25 def CheckDoubleHash(numList, hashList):
26     if None == hashList:
27         return;
28 
29     for k in numList:
30         for j in range(m):
31             g_k = (k + 1) % (m - 2);
32             hj_k = (k + j * g_k) % m;
33             #check the key is equal with the one in hash list, if not check the next one
34             if hashList[hj_k] == k:
35                 if j > 0:
36                     print k, "find", j+1, "times";
37                 break;
38             else:
39                 print k, "conflict with", hashList[hj_k]; 
40         else:
41             print k, "is not find...";
42             return False;
43 
44     return True;

 

測試時,設置了m = 19

測試數列為: numList = [0, 1, 2, 7, 9, 15, 19, 20, 77, 38],為了測試沖突,故意設置了一些沖突數,為了減少無用的輸出,對於沒有沖突的就不打出了,測試結果如下:

image

只有38在發生了沖突1次,而且,所有的球號分散相對單散列函數分布要分散得多,說明雙重散列函數分散效果均勻性更好,從而沖突率是非常低的

同時,注意,實際上38 < 17*19, 仍然發生沖突了,原因就是38和0沖突后,h1(38) = 20,又和20沖突,從而導致其最終經過雙重散列后仍有沖突

 

再看下雙重散列函數對第2部分的聚集沖突的連續數列處理結果:

numList = [0, 1, 2, 19, 20, 21, 18, 44, 17, 38, 77];

可以發現,沖突大大減少,沒有高於3次的查找,且連續數列{19, 20, 21}被分散開來,有效的減少了單散列函數聚集沖突問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM