11. 哈希表(1)

python中字典和集合內部用的就是哈希表(散列表)因此他們的查找速度很是快。python

數組能夠迅速經過下標去查找,O(1);可是刪除數組元素就要向前依次移動其餘元素,因此O(n);數組

循環雙端鏈表在知道一個節點的狀況下迅速刪除它,O(1),可是它的節點查找又成了 O(n),比較慢。bash


擴展:
ide

給元素一個邏輯下標,這樣在存儲一個元素的時候,經過這個元素計算一個邏輯下標,這樣就能夠迅速經過這個下標找到這個元素,這也就是哈希表的工做原理。函數


舉例:spa

有個長度爲13個元素的數組,定義一個哈希函數orm

h(key) = key % Mblog

使用取餘運算是的h(key)的結果不會超過數組長度的下標,再來分別插入如下元素:ip

765, 431, 96, 142, 579, 226, 903, 388ci


先來計算下它們應用哈希函數後的結果:

M = 13
h(765) = 765 % M = 11
h(431) = 431 % M = 2
h(96) = 96 % M = 5
h(142) = 142 % M = 12
h(579) = 579 % M = 7
h(226) = 226 % M = 5
h(903) = 903 % M = 6
h(388) = 388 % M = 11

能夠看到 96 和 226,以及 765 和 388 產生了哈希衝突。

下圖模擬哈希表的插入過程(連接法):

image.png

當出現哈希衝突的時候,就把這個位置當成連接表,226就往這個連接表裏面插入,因此用連接法解決哈希衝突,缺點也很明顯,若是哈希衝突的比較多,致使這個鏈過長,這時去查找元素的時候,無法經過O(1)時間去定位,就發生了時間複雜度退化的狀況。


另一種方法就相對較好:開放尋址法

它的思想比較簡單,當一個槽被佔用的時候,採用一種方式尋找下一個可用的槽,這裏的槽指的是數組中的一個位置,根據找下一個槽的方式不一樣分爲幾種:

(1)線性探查:當一個槽被佔用,找下一個可用的槽

        h(k, i) = (h'(k) +i) % m, i=0,1,2,....,m-1

        #k 是插入元素的key值

        #i 是位置

        #h'(k) + i  是對M取模

        #m-1 是槽的長度


(2)2次探查:【python內置使用的就是二次探查】

        當一個槽被佔用時,以2次方做爲偏移量

        h(k,i)=(h(k)+c1+c2i2)%m,i=0,1,...,m−1

        選取c1,c2兩個常數,以前計算獲得的一個哈希表的位置(h'(k))以後,加上c1和c2i2的和,對m取模,以2次方做爲偏移量。


(3)雙重散列:從新計算哈希結果

        h(k,i)=(h1(k)+ih2(k))%m

        這種方法根據哈希函數進行二次哈希,用的不多


python實際上使用的是二次探查法:

h(k, i) = (home +i2) % m

意思是若是遇到了衝突,就在原始計算的位置上不斷加上i的平方。以後再對m使用取模。


下面的代碼模擬了計算邏輯下標的過程:

inserted_index_set = set()
M = 13
def h(key, M=13):
    return key % M

to_insert = [765, 431, 96, 142, 579, 226, 903, 388]
for number in to_insert:
    index = h(number)
    first_index = index
    i = 1
    while index in inserted_index_set:               #若是計算髮現已經佔用,繼續計算獲得下一個可用槽的位置
        print('\th({number}) = {number} % M = {index} collision'.format(number=number, index=index))
        index = (first_index +  i*i) % M          #根據二次方探查的公式從新計算下一個須要插入的位置
        i += 1
    else:
        print('h({number}) = {number} % M = {index}'.format(number=number, index=index))
        inserted_index_set.add(index)

這段代碼輸出的結果以下:

h(765) = 765 % M = 11

h(431) = 431 % M = 2

h(96) = 96 % M = 5

h(142) = 142 % M = 12

h(579) = 579 % M = 7    

h(226) = 226 % M = 5 collision

h(226) = 226 % M = 6    

h(903) = 903 % M = 6 collision    

h(903) = 903 % M = 7 collision

h(903) = 903 % M = 10    

h(388) = 388 % M = 11 collision    

h(388) = 388 % M = 12 collision    

h(388) = 388 % M = 2 collision    

h(388) = 388 % M = 7 collision

h(388) = 388 % M = 1


遇到衝突以後會從新計算,每一個待插入元素最終的下標就是:

h(765) => 11

h(431) => 2

h(96) => 5

h(142) => 12

h(579) => 7

h(226) => 5(collision) => 6

h(903) => 6(collision) => 7(collision) => 10

h(338) => 11(collision) => 12(collision) => 2(collision) => 7(collision) => 1


詳細解釋以下:

h(226)的邏輯下標是5,衝突了,因此5+12=6,查看6的位置爲空,OK,找到位置了;

h(903)的邏輯下標是6,衝突了,因此6+12=7,7衝突,繼續,6+22=10,10的位置爲空,找到位置了;

h(388)的邏輯下標是11,衝突了,因此11+12=12,也衝突,11+22=15,15%13=2,2衝突,繼續,11+32= 20,20%13=7,7衝突,繼續,11+42=27,27%13=1, 1的位置爲空,找到位置了;


image.png

以上就是解決哈希衝突的方式。

相關文章
相關標籤/搜索