1-hash哈希介紹
hash函數,把任意長度的輸入通過散列算法變換成固定長度的輸出,該輸出就是散列值1。一種常見的hash函數是,一般取素數。
設hash函數的定義域爲,值域爲,一般來說,,這樣hash函數容易出現碰撞,如下圖,,在一條鏈上(碰撞):
對於hash函數,基本上都能找到一組輸入,使得它們的hash值都相同,導致它們在一條鏈上,有時甚至會比線性查找的複雜度還要高,因爲比線性查找多了hash的時間。
2-Universal hashing全域哈希法
思路:解決上述問題的一種方法就是隨機。隨機從一組hash函數(a family of hash functions)中選擇一個。這樣選的話,攻擊者就沒辦法針對特定的hash函數構造一組輸入,使得hash函數效率很低。
定義1:是定義域,是hash函數的集合,能夠將映射到,即.
定義2:如果滿足並且,則稱是全域(universal)的。
根據定義2,如果h是隨機均勻地從中選擇(注意每個輸入要重新選擇一個hash函數), 那麼和碰撞的概率是:
定理1:隨機均勻地從(是全域的)選擇,如果我們現在已經把個輸入放入了hash表中了,則再給一個輸入,有
其中表示期望。
[定理1的重要性] 通過證明上述定理,我們就可以說,如果存在是全域的,那麼最終在hash表中元素的分佈(在平均意義上)是均勻的。
定理1的證明. 設表示在hash表中的隨機元素和碰撞的數量,設
那麼,
例子 :如果,則
3-構造一個全域哈希
定理2: 按照如下四個步驟構造的是全域的:
- (條件)令等於一個素數;
- (初始準備)將輸入寫成個數字:,其中(等價於將用進製表示);
- (隨機)隨機選擇一個,其中;
- (hash函數).
證明見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
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])