Universal Hashing全域哈希原理與python實現,減少hash衝突/碰撞!

1-hash哈希介紹

hash函數y=h(k)y=h(k),把任意長度的輸入kk通過散列算法hh變換成固定長度的輸出yy,該輸出就是散列值1。一種常見的hash函數是y=H(k)=(ak+b)mod  my=H(k)=(a\cdot k+b) \mod mmm一般取素數。
設hash函數的定義域爲KK,值域爲YY,一般來說,K>Y|K|>|Y|,這樣hash函數容易出現碰撞,如下圖,h(k5)=h(k2)=h(k7)h(k_5)=h(k_2)=h(k_7)k5,k2,k7k_5,k_2,k_7在一條鏈上(碰撞):

在這裏插入圖片描述
對於hash函數,基本上都能找到一組輸入,使得它們的hash值都相同,導致它們在一條鏈上,有時甚至會比線性查找的複雜度還要高,因爲比線性查找多了hash的時間。

2-Universal hashing全域哈希法

思路:解決上述問題的一種方法就是隨機。隨機從一組hash函數(a family of hash functions)中選擇一個。這樣選的話,攻擊者就沒辦法針對特定的hash函數構造一組輸入,使得hash函數效率很低。

定義1U\mathcal{U}是定義域,H\mathcal{H}是hash函數的集合,能夠將U\mathcal{U}映射到{0,1,...,m1}\{0, 1, ..., m-1\},即h:U{0,1,...,m1},hHh:\mathcal{U}\rightarrow\{0, 1, ..., m-1\}, h\in \mathcal{H}.

定義2:如果x,y\forall x, y滿足xyx\neq y並且{hH:h(x)=h(y)}=Hm|\{h\in \mathcal{H}:h(x)=h(y)\}|=\frac{|\mathcal{H}|}{m},則稱H\mathcal{H}是全域(universal)的。

根據定義2,如果h是隨機均勻地從H\mathcal{H}中選擇(注意每個輸入要重新選擇一個hash函數), 那麼xxyy碰撞的概率是:
h(x)=h(y)=HmH=1m.\frac{h(x)=h(y)的函數數量}{所有的函數} =\frac{\frac{|\mathcal{H}|}{m}}{|\mathcal{H}|}=\frac{1}{m}.

定理1:隨機均勻地從H\mathcal{H}H\mathcal{H}是全域的)選擇hh,如果我們現在已經把nn個輸入放入了hash表TT中了,則再給一個輸入xx,有
E[hashTx]<nm,E[hash表T中元素和x碰撞的數量]<\frac{n}{m},
其中E[]E[\cdot]表示期望。

[定理1的重要性] 通過證明上述定理,我們就可以說,如果存在H\mathcal{H}是全域的,那麼最終在hash表TT中元素的分佈(在平均意義上)是均勻的。

定理1的證明.CxC_{x}表示在hash表TT中的隨機元素和xx碰撞的數量,設
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.
那麼,
E[Cx]=E[yTxCxy]=yTxE[Cxy]=yTx1m=(n1)1m<nm.\begin{array}{lll} 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}

例子 :如果n=1,m=2n=1,m=2,則E[Cx]<12.E[C_x]<\frac{1}{2}.

3-構造一個全域哈希H\mathcal{H}

定理2: 按照如下四個步驟構造的H\mathcal{H}是全域的:

  1. (條件)令mm等於一個素數;
  2. (初始準備)將輸入kk寫成r+1r+1個數字:k=<k0,k1,...,kr>k=<k_0,k_1,...,k_r>,其中ki{0,1,...,m1}k_i\in\{0, 1, ..., m-1\}(等價於將kkmm進製表示);
  3. (隨機)隨機選擇一個a=<a0,a1,...,ar>a=<a_0, a_1,...,a_r>,其中ai0,1,...,m1a_i\in{0, 1,..., m-1}
  4. (hash函數)ha(k)=(i=0i=rai×ki)mod  mh_a(k)=(\sum_{i=0}^{i=r}a_i\times k_i) \mod m.

證明見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])

  1. https://baike.baidu.com/item/Hash/390310?fr=aladdin ↩︎

  2. http://cs-www.bu.edu/faculty/homer/537/talks/SarahAdelBargal_UniversalHashingnotes.pdf 或者https://download.csdn.net/download/MustImproved/12275636 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章