python哈希表(字典)實現

1. 概念

哈希表(hash table),又稱散列表,是根據鍵key直接訪問內存存儲位置的數據結構。關鍵字經過散列函數,得到鍵key。

給定一對(關鍵字,值),關鍵字經過散列函數轉換,得到存儲位置,該存儲位置存儲(關鍵字,值)。

2. 常見的散列函數

散列函數的性質:
如果兩個散列值是不相同的(根據同一函數),那麼這兩個散列值的原始輸入也是不相同的。

6種散列函數:

  • 直接定址法

取關鍵字的某個線性函數值爲散列地址。即 hash(k)=ak+b{\displaystyle hash(k)=a\cdot k+b} .

  • 數字分析法

假設關鍵字是以r爲基的數,並且哈希表中可能出現的關鍵字都是事先知道的,則可取關鍵字的若干數位組成哈希地址。

  • 平方取中法

取關鍵字平方後的中間幾位爲哈希地址。
通常在選定哈希函數時不一定能知道關鍵字的全部情況,取其中的哪幾位也不一定合適,而一個數平方後的中間幾位數和數的每一位都相關,由此使隨機分佈的關鍵字得到的哈希地址也是隨機的。取的位數由表長決定。

  • 摺疊法

將關鍵字分割成位數相同的幾部分(最後一部分的位數可以不同),然後取這幾部分的疊加和(捨去進位)作爲哈希地址。

  • 隨機數法

選擇一隨機函數,取關鍵字的隨機值作爲散列地址,通常用於關鍵字長度不同的場合。

  • 除留餘數法

取關鍵字被某個不大於散列表表長m的數p除後所得的餘數爲散列地址。即hash(k)=kmod  p,pm{\displaystyle hash(k)=k\,{\bmod {\,}}p} , p\leq m。不僅可以對關鍵字直接取模,也可在摺疊法、平方取中法等運算之後取模。對p的選擇很重要,一般取素數或m,若p選擇不好,容易產生衝突。

3. 處理衝突幾個方法

介紹三種方法:

  • 開放定址法

hashi=(hash(key)+di)mod  m,i=1,2...k(km1){\displaystyle hash_{i}=(hash(key)+d_{i})\,{\bmod {\,}}m}, {\displaystyle i=1,2...k\,(k\leq m-1)},其中 hash(key){\displaystyle hash(key)}爲散列函數, m{\displaystyle m} 爲散列表長, di{\displaystyle d_{i}} 爲增量序列, i{\displaystyle i} i爲已發生衝突的次數。

增量序列可以是,線性函數,平方函數,隨機函數的序列。

  • 再散列

對散列後的衝突值,再次進行散列。

  • 鏈表法

關鍵字經過散列函數後的值相同,用鏈表來存儲該內存位置的所有值。一般在某個內存位置,衝突的值較多時,比如,大於8個,用紅黑樹來存儲。

4. 哈希表實現

用python實現一個簡單的哈希表,在python中,哈希表對應的是字典。

整個哈希表,除留餘數法作爲散列函數,同時,遇到衝突使用開放定址法解決。同時,動態擴容哈希表,當存儲的數量達到容量的設定閾值時,進行擴容。


class Dict():
    def __init__(self):
        self.capacity = 11
        self.load_factor = 0.75
        self.size = 0
        self.mod = self.capacity
        self.hash_table = [(None,None) for i in range(self.capacity)]
        
    def mod_function(self, x):
        return x%self.mod
        
    def hash(self,x):
        return self.mod_function(x)
    
    def resize(self):
        new_capacity = int(self.capacity*1.5)
        new_hash_table = [(None,None) for _ in range(new_capacity)]
        for (key,value) in self.hash_table:
            if key:
                hash_k = self.hash(key)
                if new_hash_table[hash_k][0] is None:
                    new_hash_table[hash_k] = (key,value)
                else:
                    for i in range(1,new_capacity):
                        hash_k = self.hash(key+i)
                        if new_hash_table[hash_k][0] is None:
                            new_hash_table[hash_k] = (key,value)
        self.capacity = new_capacity
        del self.hash_table
        self.hash_table = new_hash_table
        
    def put(self, key, value):
        hash_k = self.hash(key)
        # 存儲數據與容量比大於載荷因子,擴容
        if self.size/self.capacity > self.load_factor:
            self.resize()
        # 衝突處理
        if self.hash_table[hash_k][0] is None:
            self.hash_table[hash_k] = (key,value)
        else:
            for i in range(self.capacity):
                hash_k = self.hash(key+i)
                if self.hash_table[hash_k][0] is None:
                    self.hash_table[hash_k] = (key,value)
        self.size += 1
        
    def get(self, key):
        hash_k = self.hash(key)
        # 判斷是否衝突
        h_key, value = self.hash_table[hash_k]
        if h_key == key:
            return value
        else:
            for i in range(self.capacity):
                hash_k = self.hash(key+i)
                h_key, value = self.hash_table[hash_k]
                if h_key == key:
                    return value
        return -1
    
dict = Dict()
for i in range(11):
    dict.put(i,"hello{}".format(i))
print(dict.capacity)
print(dict.get(2))

運行結果:

16
hello2

參考:

  1. wiki Hashtable;
  2. python123 數據結構入門 哈希表
  3. 知乎 python dict和hashtable的區別
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章