1. 哈希表(Hash tables)
在Python中,字典是通過哈希表實現的。也就是說,字典是一個數組,而數組的索引是經過哈希函數處理后得到的。哈希函數的目的是使鍵均勻地分布在數組中。由於不同的鍵可能具有相同的哈希值,即可能出現沖突,高級的哈希函數能夠使沖突數目最小化。Python中並不包含這樣高級的哈希函數,幾個重要(用於處理字符串和整數)的哈希函數通常情況下均是常規的類型:
>>> map(hash, (0, 1, 2, 3)) [0, 1, 2, 3] >>> map(hash, ("namea", "nameb", "namec", "named")) [-1658398457, -1658398460, -1658398459, -1658398462]
如果在Python中運行 hash('a')
,后台將執行 string_hash()
函數,然后返回 12416037344 (這里我們假設采用的是64位的平台)。
如果用長度為 x 的數組存儲鍵/值對,則我們需要用值為 x-1 的掩碼計算槽(slot,存儲鍵/值對的單元)在數組中的索引。這可使計算索引的過程變得非常迅速。字典結構調整長度的機制(以下會詳細介紹)會使找到空槽的概率很高,也就意味着在多數情況下只需要進行簡單的計算。假如字典中所用數組的長度是 8 ,那么鍵'a'
的索引為:hash('a') & 7 = 0
,同理'b'
的索引為 3 ,'c'
的索引為 2 , 而'z'
的索引與'b'
相同,也為 3 ,這就出現了沖突。
可以看出,Python的哈希函數在鍵彼此連續的時候表現得很理想,這主要是考慮到通常情況下處理的都是這類形式的數據。然而,一旦我們添加了鍵'z'
就會出現沖突,因為這個鍵值並不毗鄰其他鍵,且相距較遠。當然,我們也可以用索引為鍵的哈希值的鏈表來存儲鍵/值對,但會增加查找元素的時間,時間復雜度也不再是 O(1) 了。下一節將介紹Python的字典解決沖突所采用的方法。
2. dict與set的實現原理
dict與set實現原理是一樣的,都是將實際的值放到list中。唯一不同的在於hash函數操作的對象,對於dict,hash函數操作的是其key,而對於set是直接操作的它的元素,假設操作內容為x,其作為因變量,放入hash函數,通過運算后取list的余數,轉化為一個list的下標,此下標位置對於set而言用來放其本身,而對於dict則是創建了兩個list,一個list該下表放此key,另一個list中該下標方對應的value。
其中,我們把實現set的方式叫做Hash Set,實現dict的方式叫做Hash Map/Table(注:map指的就是通過key來尋找value的過程)
3.hash碰撞及其解決方法
(1)開放尋址法(Open addressing)
開放尋址法是一種用探測手段處理沖突的方法。在上述鍵'z'
沖突的例子中,索引 3 在數組中已經被占用了,因而需要探尋一個當前未被使用的索引。增加和搜尋鍵/值對需要的時間均為 O(1)。
(2)拉鏈法
原理圖如下,其實就是將發生有沖突的元素放到同一位置,然后通過“指針“來串聯起來
參考文獻:
【2】python 下的數據結構與算法---8:哈希一下【dict與set的實現】