全域哈希原理與實現
1-hash哈希介紹
2-Universal hashing全域哈希法
3-構造一個全域哈希H\mathcal{H}H
4-python實現
1-hash哈希介紹
hash函數y=h(k)y=h(k)y=h(k),把任意長度的輸入kkk通過散列算法hhh變換成固定長度的輸出yyy,該輸出就是散列值1。一種常見的hash函數是y=H(k)=(a⋅k+b)mod my=H(k)=(a\cdot k+b) \mod my=H(k)=(a⋅k+b)modm,mmm一般取素數。
設hash函數的定義域爲KKK,值域爲YYY,一般來說,∣K∣>∣Y∣|K|>|Y|∣K∣>∣Y∣,這樣hash函數容易出現碰撞,如下圖,h(k5)=h(k2)=h(k7)h(k_5)=h(k_2)=h(k_7)h(k5)=h(k2)=h(k7),k5,k2,k7k_5,k_2,k_7k5,k2,k7在一條鏈上(碰撞):
對於hash函數,基本上都能找到一組輸入,使得它們的hash值都相同,導致它們在一條鏈上,有時甚至會比線性查找的複雜度還要高,因爲比線性查找多了hash的時間。
2-Universal hashing全域哈希法
思路:解決上述問題的一種方法就是隨機。隨機從一組hash函數(a family of hash functions)中選擇一個。這樣選的話,對方就沒辦法針對特定的hash函數構造一組輸入,使得hash函數效率很低。
定義1:U\mathcal{U}U是定義域,H\mathcal{H}H是hash函數的集合,能夠將U\mathcal{U}U映射到{0,1,...,m−1}\{0, 1, ..., m-1\}{0,1,...,m−1},即h:U→{0,1,...,m−1},h∈Hh:\mathcal{U}\rightarrow\{0, 1, ..., m-1\}, h\in \mathcal{H}h:U→{0,1,...,m−1},h∈H.
定義2:如果∀x,y\forall x, y∀x,y滿足x≠yx\neq yx=y並且∣{h∈H:h(x)=h(y)}∣=∣H∣m|\{h\in \mathcal{H}:h(x)=h(y)\}|=\frac{|\mathcal{H}|}{m}∣{h∈H:h(x)=h(y)}∣=m∣H∣,則稱H\mathcal{H}H是全域(universal)的。
根據定義2,如果h是隨機均勻地從H\mathcal{H}H中選擇(注意每個輸入要重新選擇一個hash函數), 那麼xxx和yyy碰撞的概率是:
h(x)=h(y)的函數數量所有的函數=∣H∣m∣H∣=1m.\frac{h(x)=h(y)的函數數量}{所有的函數}
=\frac{\frac{|\mathcal{H}|}{m}}{|\mathcal{H}|}=\frac{1}{m}.所有的函數h(x)=h(y)的函數數量=∣H∣m∣H∣=m1.
定理1:隨機均勻地從H\mathcal{H}H(H\mathcal{H}H是全域的)選擇hhh,如果我們現在已經把nnn個輸入放入了hash表TTT中了,則再給一個輸入xxx,有
E[hash表T中元素和x碰撞的數量]
其中E[⋅]E[\cdot]E[⋅]表示期望。
[定理1的重要性] 通過證明上述定理,我們就可以說,如果存在H\mathcal{H}H是全域的,那麼最終在hash表TTT中元素的分佈(在平均意義上)是均勻的。
定理1的證明. 設CxC_{x}Cx表示在hash表TTT中的隨機元素和xxx碰撞的數量,設
Cxy={1if h(x)=h(y)0if h(x)≠h(y)C_{xy}=\left\{\begin{array}{cr}
1 & if\ h(x)=h(y) \\
0 & if\ h(x)\neq h(y)
\end{array}\right.Cxy={10if h(x)=h(y)if h(x)=h(y)
那麼,
E[Cx]=E[∑y∈T−xCxy]=∑y∈T−xE[Cxy]因爲期望的線性性質=∑y∈T−x1m=(n−1)1m
E[C_x]&=E[\sum_{y\in T-x}C_{xy}] \\
&=\sum_{y\in T-x}E[C_{xy}] & 因爲期望的線性性質\\
&=\sum_{y\in T-x}\frac{1}{m} \\
&=(n-1)\frac{1}{m} \\
&<\frac{n}{m}.
\end{array}E[Cx]=E[∑y∈T−xCxy]=∑y∈T−xE[Cxy]=∑y∈T−xm1=(n−1)m1
例子 :如果n=1,m=2n=1,m=2n=1,m=2,則E[Cx]<12.E[C_x]<\frac{1}{2}.E[Cx]<21.
3-構造一個全域哈希H\mathcal{H}H
定理2: 按照如下四個步驟構造的H\mathcal{H}H是全域的:
(條件)令mmm等於一個素數;
(初始準備)將輸入kkk寫成r+1r+1r+1個數字:k=k=k=,其中ki∈{0,1,...,m−1}k_i\in\{0, 1, ..., m-1\}ki∈{0,1,...,m−1}(等價於將kkk用mmm進製表示);
(隨機)隨機選擇一個a=a=a=,其中ai∈0,1,...,m−1a_i\in{0, 1,..., m-1}ai∈0,1,...,m−1;
(hash函數)ha(k)=(∑i=0i=rai×ki)mod mh_a(k)=(\sum_{i=0}^{i=r}a_i\times k_i) \mod mha(k)=(∑i=0i=rai×ki)modm.
證明見2。
4-python實現
自己寫的代碼,如有錯誤望指正。代碼鏈接:https://github.com/VFVrPQ/LDP/blob/master/Components/UniversalHashing.py,另有完整代碼如下:
import math
import random
class UniversalHashing:
'''
g: a prime
d: domain, [0, 1, ..., d-1]
len: The maximum number of digits in g Base
v: an input value in [0, 1, ..., d-1]
hash function: H_a(k) = (a(0)*k(0)+a(1)*k(1)+...+a(len-1)*k(len-1)) % g
'''
def __init__(self, g, d):
self.__g = g
assert g>=2, 'g is less than 2'
assert self.__isPrime(g), 'g is not a prime'
self.__d = d
self.__len = math.ceil( math.log(d) / math.log(g)) # g進制下,最大的位數
self.__a = self.__len*[0] # initial length
# v is an input value in [0, 1, ..., d-1]
def hash(self, v):
self.__randomness() # regenerate a, select H
out = self.calc(self.__a, v)
return self.__a, out
# calc H_a(k) = (a(0)*k(0)+a(1)*k(1)+...+a(len-1)*k(len-1)) % g
def calc(self, a, v):
assert len(a)==self.__len, 'len(a)!=self.__len'
k = self.__toBitList(v)
out = 0鄭州人流手術多少錢 http://mobile.sgyy029.com/
for i in range(self.__len):
out = (out + a[i]*k[i]) % self.__g
return out
def __randomness(self):
# generate a
for i in range(self.__len):
self.__a[i] = random.randint(0, self.__g-1)
def __toBitList(self, v):
assert v>=0, 'v<0'
if v == 0:
return self.__len * [0]
bitList = self.__len * [0]
for i in range(self.__len):
bitList[i] = v%self.__g
v = int(v/self.__g)
return bitList
def __isPrime(self, v):
if v<=1:
return False
for i in range(2, int(math.sqrt(v))+1, 1):
if v%i==0:
return False
return True
# for test
if __name__ == "__main__":
TIMES = 10
g = 29 # prime
d = 16 # domain
uhash = UniversalHashing(g, d)
H = g * [0]
for i in range(TIMES): # random TIMES to verify
x = random.randint(0, d-1)
_, out = uhash.hash(x)
H[out] += 1
for i in range(g):
print(i, H[i])