天天看點

Python 散清單查詢_進入<哈希函數>為結界的世界

1. 前言

哈希表

或稱為

散清單

,是一種常見的、使用頻率非常高的資料存儲方案。

哈希表

屬于抽象資料結構,需要開發者按

哈希表

資料結構的存儲要求進行

API

定制,對于大部分進階語言而言,都會提供已經實作好的、可直接使用的

API

,如

JAVA

中有

MAP

集合、

C++

中的

MAP

容器,

Python

中的字典……

使用者可以使用

API

中的方法完成對

哈希表

的增、删、改、查……一系列操作。

如何學習哈希表?

可以從

2

個角度開始:

  • **使用者角度:**隻需要知道

    哈希表

    是基于

    對存儲的解決方案,另需要熟悉不同計算機語言提供的基于

    哈希表

    資料結構的

    API

    實作,學會使用

    API

    中的方法。
  • 開發者的角度:則需要知道

    哈希表

    底層實作原理,以及實作過程中需要解決的各種問題。本文将站在開發者的角度,帶着大家一起探究

    哈希

    的世界。

2. 哈希表

什麼是哈希表?

哈希表

是基于

對存儲的資料結構,底層一般采用的是

清單(數組)

大家都知道,基于

清單(數組)

的查詢速度非常快,時間複雜度是

O(1)

,常量級别的。

清單的底層存儲結構是連續的記憶體區域,隻要給定資料在清單(數組)中的位置,就能直接查詢到資料。理論上是這麼回事,但在實際操作過程,查詢資料的時間複雜度卻不一定是常量級别的。

如存儲下面的學生資訊,學生資訊包括學生的姓名和學号。在存儲學生資料時,如果把學号為

的學生存儲在清單

位置,學号為

1

的學生存儲在清單

1

位置……

Python 散清單查詢_進入<哈希函數>為結界的世界

這裡把學生的學号和清單的索引号進行關聯,查詢某一個學生時,知道了學生的學号也就知道了學生資料存儲在清單中的位置,可以認為查詢的時間複雜度為

O(1)

之是以可以達到常量級,是因為這裡有資訊關聯(學生學号關聯到資料的存儲位置)。

還有一點,學生的學号是公開資訊也是常用資訊,很容易擷取。

但是,不是存儲任何資料時,都可以找到與清單位置相關聯的資訊。比如存儲所有的英文單詞,不可能為每一個英文單詞編号,即使編号了,編号在這裡也僅僅是流水号,沒有資料含義的資料對于使用者來講是不友好,誰也無法記住哪個英文單詞對應哪個編号。

是以使用清單存儲英文單詞後需要詢時,因沒有單詞的存儲位置。還是需要使用如

線性

二分

……之類的查詢算法,這時的時間複雜度由使用的查詢算法的時間複雜度決定。

如果對上述存儲在清單的學生資訊進行了

插入

删除

……等操作,改變了資料原來的位置後,因破壞了學号與位置關聯資訊,再查詢時也隻能使用其它查詢算法,不可能達到常量級。

是否存在一種方案,能最大化地優化資料的存儲和查詢?

通過上述的分析,可以得出一個結論,要提高查詢的速度,得想辦法把

資料

位置

進行關聯。而

哈希表

的核心思想便是如此。

2.1 哈希函數

哈希表

引入了

關鍵字

概念,

關鍵字

可以認為是資料的别名。如上表,可以給每一個學生起一個别名,這個就是

關鍵字

Python 散清單查詢_進入<哈希函數>為結界的世界
Tip: 這裡的

關鍵字

是姓名的

拼音

縮寫,

關鍵字

資料

的關聯性較強,友善記憶和查詢。

有了

關鍵字

後,再把

關鍵字

映射成清單中的一個有效位置,映射方法就是

哈希表

中最重要的概念

哈希函數

關鍵字

是一個橋梁,即關聯到真正資料又關聯到哈希表中的位置。

關鍵字

也可以是需要儲存的資料本身。

哈希函數

的功能:提供把

關鍵字

映射到

清單

中的位置算法,是

哈希表

存儲資料的核心所在。如下圖,示範

資料

哈希函數

哈希表

之間的關系,可以說

哈希函數

是資料進入

哈希表

的入口。

Python 散清單查詢_進入<哈希函數>為結界的世界

資料最終會存儲在清單中的哪一個位置,完全由

雜湊演算法

決定。

當需要查詢學生資料時,同樣需要調用

哈希函數

關鍵字

進行換算,計算出資料在清單中的位置後就能很容易查詢到資料。

如果忽視

哈希函數

的時間複雜度,基于

哈希表

的資料存儲和查詢時間複雜度是

O(1)

如此說來

哈希函數

算法設計的優劣是影響

哈希表

性能的關鍵所在。

2.2 雜湊演算法

雜湊演算法

決定了資料的最終存儲位置,不同的

雜湊演算法

設計方案,也關乎

哈希表

的整體性能,是以,

雜湊演算法

就變得的尤為重要。

下文将介紹并縱橫比較幾種常見的

雜湊演算法

的設計方案。

Tip: 無論使用何種

雜湊演算法

,都有一個根本,

哈希

後的結果一定是一個數字,表示清單(哈希表)中的一個有效位置。也稱為

哈希值

使用

哈希表

存儲資料時,

關鍵字

可以是數字類型也可以是非數字類型,其實,

關鍵字

可以是任何一種類型。這裡先讨論當

關鍵字

為非數字類型時設計

雜湊演算法

的基本思路。

如前所述,已經為每一個學生提供了一個以姓名的拼音縮寫的

關鍵字

現在如何把

關鍵字

映射到

清單

的一個有效位置?

這裡可以簡單地把拼音看成英文中的字母,先分别計算每一個字母在字母表中的位置,然後相加,得到的一個數字。

使用上面的

哈希思想

對每一個學生的

關鍵字

進行哈希:

  • zjl

    的哈希值為

    26+10+12=48

  • llj

    的哈希值為

    12+12+10=34

  • cl

    的哈希值為

    3+12=15

  • zxy

    的哈希值為

    26+25+24=75

前文說過

哈希值

是表示資料在清單中的存儲位置,現在假設一種理想化狀态,學生的姓名都是

3

個漢字,意味着

關鍵字

也是

3

個字母,采用上面的的

雜湊演算法

,最大的

哈希值

應該是

zzz=26+26+26=78

,意味着至少應該提供一個長度為

78

的清單 。

如果,現在僅僅隻儲存

4

名學生,雖然隻有

4

名學生,因無法保證學生的

關鍵字

不出現

zzz

,是以清單長度還是需要

78

。如下圖所示。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-sY2XOwRg-1652143602408)(https://s2.51cto.com/images/20220510/1652143515926702.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)]

采用這種

雜湊演算法

會導緻清單的空間浪費嚴重,最直覺想法是對

哈希值

再做限制,如除以

4

再取餘數,把

哈希值

限制在

4

之内,

4

個資料對應

4

個哈希值。我們稱這種取餘數方案為

取餘數算法

取餘數法中,被除數一般選擇小于哈希表長度的素數。本文介紹其它雜湊演算法時,也會使用取餘數法對哈希值進行适當範圍的收縮。

重新對

4

名學生的關鍵字進行哈希。

  • zjl

    的哈希值為

    26+10+12=48

    48

    除以

    4

    取餘數,結果是 。
  • llj

    的哈希值為

    12+12+10=34

    34

    除以

    4

    取餘數,結果是

    2

  • cl

    的哈希值為

    3+12=15

    15

    除以

    4

    取餘數,結果是

    3

  • zzz

    的哈希值為

    26+26+26=78

    78

    除以

    4

    取餘數,結果是

    2

Python 散清單查詢_進入<哈希函數>為結界的世界

示範圖上出現了一個很奇怪的現象,沒有看到

李連傑

的存儲資訊。

4

個存儲位置存儲

4

學生,應該是剛剛好,但是,隻存儲了

3

名學生。且還有

1

個位置是空閑的。現在編碼驗證一下,看是不是人為因素引起的。

'''
哈希函數
'''
def hash_code(key):
    # 設定字母 A 的在字母表中的位置是 1
    pos = 0
    for i in key:
        i = i.lower()
        res = ord(i) - ord('a') + 1
        pos += res
    return pos % 4
           

測試代碼:

# 哈希表
hash_table = [None] * 4
# 計算關鍵字的哈希值
idx = hash_code('zjl')
# 根據關鍵字換算出來的位置存儲資料
hash_table[idx] = '周傑倫'
idx = hash_code('llj')
hash_table[idx] = '李連傑'
idx = hash_code('cl')
hash_table[idx] = '成龍'
idx = hash_code('zzz')
hash_table[idx] = '張志忠'
print('哈希表中的資料:', hash_table)
'''
輸出結果:
哈希表中的資料: ['周傑倫', None, '張志忠', '成龍']
'''
           

執行代碼,輸出結果,依然還是沒有看到

李連傑

的資訊。

原因何在?

這是因為

李連傑

張志忠

的哈希值都是

2

,導緻在存儲時,後面存儲的資料會覆寫前面存儲的資料,這就是哈希中的典型問題,

哈希沖突問題

所謂

哈希沖突

,指不同的

關鍵字

在進行

雜湊演算法

後得到相同的

哈希值

,這意味着,不同

關鍵字

所對應的資料會存儲在同一個位置,這肯定會發生資料丢失,是以需要提供算法,解決沖突問題。

Tip: 研究

哈希表

,歸根結底,是研究如何計算

哈希值

以及如何解決

哈希值沖突

的問題。

針對上面的問題,有一種想當然的沖突解決方案,擴充清單的存儲長度,如把清單擴充到長度為

8

直覺思維是:擴充清單長度,哈希值的範圍會增加,沖突的可能性會降低。
'''
哈希函數
'''
def hash_code(key):
    # 設定字母 A 的在字母表中的位置是 1
    pos = 0
    for i in key:
        i = i.lower()
        res = ord(i) - ord('a') + 1
        pos += res
    return pos % 8

# 哈希表
hash_table = [None] * 8

# 儲存所有學生
idx = hash_code('zjl')
hash_table[idx] = '周傑倫'
idx = hash_code('llj')
hash_table[idx] = '李連傑'
idx = hash_code('cl')
hash_table[idx] = '成龍'
idx = hash_code('zzz')
hash_table[idx] = '張志忠'
print('哈希表中的資料:', hash_table)
'''
輸出結果:
哈希表中的資料: ['周傑倫', None, '李連傑', None, None, None, '張志忠', '成龍']
'''
           
Python 散清單查詢_進入<哈希函數>為結界的世界

貌似解決了沖突問題,其實不然,當試着設定清單的長度為

6

7

8

9

10

時,隻有當長度為

8

時沒有發生沖突,這還是在要存儲的資料是已知情況下的嘗試。

如果資料是動态變化的,顯然這種擴充長度的方案絕對不是本質解決沖突的方案。即不能解決沖突,且産生大量空間浪費。

如何解決

哈希沖突

,會在後文詳細介紹,這裡還是回到

雜湊演算法

上。

綜上所述,我們對

雜湊演算法

的理想要求是:

  • 為每一個

    關鍵字

    生成一個唯一的

    哈希值

    ,保證每一個資料都有隻屬于自己的存儲位置。
  • 雜湊演算法的性能時間複雜度要低。

現實情況是,同時滿足這

2

個條件的

雜湊演算法

幾乎是不可能有的,面對資料量較多時,

哈希沖突

是常态。是以,隻能是盡可能滿足。

因沖突的存在,即使為

100

個資料提供

100

個有效存儲空間,還是會有空間閑置。這裡把實際使用空間和清單提供的有效空間相除,得到的結果,稱之為哈希表的

占有率(載荷因子)

如上述,當清單長度為

4

時, 占有率為

3/4=0.75

,當清單長度為

8

時,占有率為

4/8=0.5

,一般要求占率控制 在

0.6~0.9

之間。

2.3 常見雜湊演算法

前面在介紹什麼是

雜湊演算法

時,提到了

取餘數法

,除此之外,還有幾種常見的

雜湊演算法

2.3.1 折疊法

**折疊法:**将

關鍵字

分割成位數相同的幾個部分(最後一部分的位數可以不同)然後取這幾部分的疊加和(舍去進位)作為哈希值。

折疊法又分移位疊加和間界疊加。

  • 移位疊加:将分割後的每一部分的最低位對齊,然後相加。
  • 間界疊加:從一端沿分割線來回折疊,然後對齊相加。

因有相加求和計算,折疊法适合數字類型或能轉換成數字類型的關鍵字。假設現在有很多商品訂單資訊,為了簡化問題,訂單隻包括訂單編号和訂單金額。

現在使用用

哈希表

存儲訂單資料,且以訂單編号為

關鍵字

,訂單金額為

訂單編号 訂單金額
20201011 400.00
19981112 300.00
20221212 200

移位疊法換算關鍵字的思路:

**第一步:**把訂單編号

20201011

按每

3

位一組分割,分割後的結果:

202、010、11

2

位一組還是

3

位一組進行分割,可以根據實際情況決定。

第二步: 把分割後的數字相加

202+010+11

,得到結果:

223

。再使用取餘數法,如果哈希表的長度為

10

,則除以

10

後的餘數為

3

這裡除以

10

僅是為了簡化問題細節,具體操作時,很少選擇清單的長度。

**第三步:**對其它的

關鍵字

采用相同的處理方案。

關鍵字 哈希值
20201011 3
19981112 2
20221212 6

編碼實作儲存商品訂單資訊:

'''
移位疊加雜湊演算法
'''
def hash_code(key, hash_table_size):
    # 轉換成字元串
    key_s = str(key)
    # 儲存求和結果
    s = 0
    # 使用切片
    for i in range(0, len(key_s), 3):
        s += int(key_s[i:i + 3])
    return s % hash_table_size

# 商品資訊
products = [[20201011, 400.00], [19981112, 300], [20221212, 200]]
# 哈希表長度
hash_size = 10
# 哈希表
hash_table = [None] * hash_size
# 以哈希表方式進行存儲
for p in products:
    key = hash_code(p[0], hash_size)
    hash_table[key] = p[1]
# 顯示哈希表中的資料
print("哈希表中的資料:",hash_table)
# 根據訂單号進行查詢
hash_val = hash_code(19981112, hash_size)
val = hash_table[hash_val]
print("訂單号為{0}的金額為{1}".format(19981112, val))
'''
輸出結果
哈希表中的資料: [None, None, 300, 400.0, None, None, 200, None, None, None]
訂單号為19981112的金額為300
'''
           

間界疊加法:

間界疊加法,會間隔地把要相加的數字進行反轉。

如訂單編号

19981112

3

位一組分割,分割後的結果:

199、811、12

,間界疊加操作求和表達式為

199+118+12=339

,再把結果

339 % 10=9

編碼實作間界疊加算法:

'''
間界疊加雜湊演算法
'''
def hash_code(key, hash_table_size):
    # 轉換成字元串
    key_s = str(key)
    # 儲存求和結果
    s = 0
    # 使用切片
    for i in range(0, len(key_s), 3):
        # 切片
        tmp_s = key_s[i:i + 3]
        # 反轉
        if i % 2 != 0:
            tmp_s = tmp_s[::-1]
        s += int(tmp_s)
    return s % hash_table_size

# 商品資訊(資料樣例)
products = [[20201011, 400.00], [19981112, 300], [20221212, 200]]
# 哈希表長度
hash_size = 10
# 哈希表
hash_table = [None] * hash_size
# 以哈希表方式進行存儲
for p in products:
    key = hash_code(p[0], hash_size)
    hash_table[key] = p[1]
# 顯示哈希表中的資料
print("哈希表中的資料:", hash_table)
# 根據訂單号進行查詢
hash_val = hash_code(19981112, hash_size)
val = hash_table[hash_val]
print("訂單号為{0}的金額為{1}".format(19981112, val))
'''
輸出結果:
哈希表中的資料: [None, None, None, 400.0, None, None, 200, None, None, 300]
訂單号為19981112的金額為300
'''
           

2.3.2 平方取中法

平方取中法:先是對

關鍵字

求平方,再在結果中取中間位置的數字。

求平方再取中算法,是一種較常見的雜湊演算法,從數學公式可知,求平方後得到的中間幾位數字與關鍵字的每一位都有關,取中法能讓最後計算出來的哈希值更均勻。

因要對

關鍵字

求平方,

關鍵字

隻能是數字或能轉換成數字的類型,至于

關鍵字

本身的大小範圍限制,要根據使用的計算機語言靈活設定。

如下面的圖書資料,圖書包括圖書編号和圖書名稱。現在需要使用哈希表儲存圖書資訊,以圖書編号為關鍵字,圖書名稱為值。

圖書編号 圖書名稱
58 python 從入門到精通
67 C++ STL
78 Java 記憶體模型

使用平方取中法計算關鍵字的哈希值:

**第一步:**對圖書編号

58

求平方,結果為

3364

第二步:取

3364

的中間值

36

,然後再使用取餘數方案。如果哈希表的長度為

10

,則

36%10=6

**第三步:**對其它的關鍵字采用相同的計算方案。

編碼實作平方取中算法:

'''
雜湊演算法
平方取中
'''
def hash_code(key, hash_table_size):
    # 求平方
    res = key ** 2
    #  取中間值,這裡取中間 2 位(簡化問題)
    res = int(str(res)[1:3])
    # 取餘數
    return res % hash_table_size

hash_table_size = 10
hash_table = [None]*hash_table_size
# 圖書資訊
books = [[58, "python 從入門到精通"], [67, "C++ STL"], [78, "Java 記憶體模型"]]
for b in books:
    hash_val = hash_code(b[0],hash_table_size)
    hash_table[hash_val]=b[1]

# 顯示哈希表中的資料
print("哈希表中的資料:", hash_table)
# 根據編号進行查詢
hash_val = hash_code(67, hash_table_size)
val = hash_table[hash_val]
print("編号為{0}的書名為{1}".format(67, val))
           

上述求平方取中間值的算法僅針對于本文提供的圖書資料,如果需要算法具有通用性,則需要根據實際情況修改。

不要被

取中

字所迷惑,不一定是絕對中間位置的數字。

2.3.3 直接位址法

直接位址法:提供一個與

關鍵字

相關聯的

線性函數

。如針對上述圖書資料,可以提供線性函數

f(k)=2*key+10

系數

2

和常數

10

的選擇會影響最終生成的哈希值的大小。可以根據哈希表的大小和操作的資料含義自行選擇。

key

為圖書編号。當

關鍵字

不相同時,使用

線性函數

得到的值也是唯一的,是以,不會産生哈希沖突,但是會要求

哈希表

的存儲長度比實際資料要大。

這種算法在實際應用中并不多見。

實際應用時,具體選擇何種

雜湊演算法

,完全由開發者定奪,

雜湊演算法

的選擇沒有固定模式可循,雖然上面介紹了幾種算法,隻是提供一種算法思路。

2.4 哈希沖突

哈希沖突

是怎麼引起的,前文已經說過。現在聊聊常見的幾種

哈希沖突

解決方案。

2.4.1 線性探測

當發生

哈希沖突

後,會在沖突位置之後尋找一個可用的空位置。如下圖所示,使用

取餘數雜湊演算法

,儲存資料到哈希表中。

哈希表的長度設定為

15

,除數設定為

13

Python 散清單查詢_進入<哈希函數>為結界的世界

解決沖突的流程:

  1. 78

    26

    的哈希值都是 。而因為

    78

    26

    的前面,

    78

    先占據哈希表的 位置。
  2. 當存儲

    26

    時,隻能以 位置為起始位置,向後尋找空位置,因

    1

    位置沒有被其它資料占據,最終儲存在哈希表的

    1

    位置。
  3. 當存儲數字

    14

    時,通過雜湊演算法計算,其哈希值是

    1

    ,本應該要儲存在哈希表中

    1

    的位置,因

    1

    位置已經被

    26

    所占據,隻能向後尋找空位置,最終落腳在

    2

    位置。

線性探測法讓發生

哈希沖突

的資料儲存在其它資料的哈希位置,如果沖突的資料較多,則占據的本應該屬于其它資料的哈希位置也較多,這種現象稱為

哈希聚集

查詢流程:

以查詢資料

14

為例。

  1. 計算

    14

    的哈希值,得到值為

    1

    ,根據哈希值在哈希表中找到對應位置。
  2. 檢視對應位置是否存在資料,如果不存在,宣告查詢失敗,如果存在,則需要提供資料比較方法。
  3. 1

    位置的資料

    26

    并不等于

    14

    。于是,繼續向後搜尋,并逐一比較。
  4. 最終可以得到結論

    14

    在哈希表的編号為

    2

    的位置。

是以,在查詢過程中,除了要提供哈希函數,還需要提供資料比較函數。

删除流程:

以删除數字

26

為例。

  1. 按上述的查詢流程找到數字

    26

    在哈希表中的位置

    1

  2. 設定位置

    1

    為删除狀态,一定要标注此位置曾經儲存過資料,而不能設定為空狀态。為什麼?
    如果設定為空狀态,則在查詢數字

    14

    時,會産生錯誤的傳回結果,會認為

    14

    不存在。為什麼?自己想想。

編碼實作線性探測法:

添加資料:

'''
線性探測法解決哈希沖突
'''
def hash_code(key, hash_table, num):
    # 哈希表的長度
    size = len(hash_table)
    # 取餘數法計算哈希值
    hash_val = key % num
    # 檢查此位置是否已經儲存其它資料
    if hash_table[hash_val] is not None:
        # 則從hash_val 之後尋找空位置
        for i in range(hash_val + 1, size + hash_val):
            if i >= size:
                i = i % size
            if hash_table[i] is None:
                hash_val = i
                break
    return hash_val

# 哈希表
hash_table = [None] * 15
src_nums = [25, 78, 56, 32, 88, 26, 73, 81, 14]
for n in src_nums:
    hash_val = hash_code(n, hash_table, 13)
    hash_table[hash_val] = n

print("哈希表中的資料:", hash_table)
'''
輸出結果:
哈希表中的資料: [78, 26, 14, 81, 56, None, 32, None, 73, None, 88, None, 25, None, None]
'''
           
**Tip:**為了保證當哈希值發生沖突後,如果從沖突位置查到哈希表的結束位置還是沒有找到空位置,則再從哈希表的起始位置,也就是 位置再搜尋到沖突位置。沖突位置是起點也是終點,建構一個查找邏輯環,以保證一定能找到空位置。
for i in range(hash_val + 1, size + hash_val):
	 pass
           

基于線性探測的資料查詢過程和存儲過程大緻相同:

def get(key, hash_table, num):
    # 哈希表的長度
    size = len(hash_table)
    # 取餘數法計算哈希值
    hash_val = key % num
    is_exist = False
    # 檢查此位置是否已經儲存其它資料
    if hash_table[hash_val] is None:
        # 不存在
        return None
    if hash_table[hash_val] != key:
        # 則從hash_val 之後尋找空位置
        for i in range(hash_val + 1, size + hash_val):
            if i >= size:
                i = i % size
            if hash_table[i] == key:
                hash_val = i
                is_exist = True
                break
    else:
        is_exist=True
    if is_exist:
        return hash_val

# 測試   
res = get(25, hash_table, 13)
print(res)
           

為了減少資料聚集,可以采用增量線性探測法,所謂

增量

指當發生哈希沖突後,探測空位置時,使用步長值大于

1

的方式跳躍式向前查找。目的是讓資料分布均勻,減小資料聚集。

除了采用增量探測之外,還可以使用

再哈希

的方案。也就是提供

2

個哈希函數,第

1

次哈希值發生沖突後,再調用第

2

個哈希函數再哈希,直到沖突不再産生。這種方案會增加計算時間。

2.4.4 連結清單法

上面所述的沖突解決方案的核心思想是,當沖突發生後,在哈希表中再查找一個有效空位置。

這種方案的優勢是不會産生額外的存儲空間,但易産生資料聚集,會讓資料的存儲不均衡,并且會違背初衷,通過

關鍵字

計算出來的

哈希值

并不能準确描述資料正确位置。

連結清單法

應該是所有解決

哈希沖突

中較完美的方案。所謂

連結清單法

,指當發生

哈希沖突

後,以沖突位置為首結點建構一條連結清單,以連結清單方式儲存所有發生沖突的資料。如下圖所示:

Python 散清單查詢_進入<哈希函數>為結界的世界

連結清單方案解決沖突,無論在存儲、查詢、删除時都不會影響其它資料位置的

獨立性

唯一性

,且因連結清單的操作速度較快,對于哈希表的整體性能都有較好改善。

使用連結清單法時,哈希表中儲存的是連結清單的首結點。首結點可以儲存資料也可以不儲存資料。

編碼實作連結清單法:連結清單實作需要定義 2 個類,1 個是結點類,1 個是哈希類。

'''
結點類
'''
class HashNode():
    def __init__(self, value):
        self.value = value
        self.next_node = None

'''
哈希類
'''
class HashTable():
    def __init__(self):
        # 哈希表,初始大小為 15,可以根據需要動态修改
        self.table = [None] * 15
        # 實際資料大小
        self.size = 0

    '''
    存儲資料
    key:關鍵字
    value:值
    '''

    def put(self, key, value):
        hash_val = self.hash_code(key)
        # 新結點
        new_node = HashNode(value)
        if self.table[hash_val] is None:
            # 本代碼采用首結點儲存資料方案
            self.table[hash_val] = new_node
            self.size+=1
        else:
            move = self.table[hash_val]
            while move.next_node is not None:
                move = move.next_node
            move.next_node = new_node
            self.size+=1

    '''
    查詢資料
    '''
    def get(self, key):
        hash_val = self.hash_code(key)
        if self.table[hash_val] is None:
            # 資料不存在
            return -1

        if self.table[hash_val].value == key:
            # 首結點就是要找的資料
            return self.table[hash_val].value

        # 移動指針
        move = self.table[hash_val].next_node
        while move.value != key and move is not None:
            move = move.next_node
        if move is None:
            return -1
        else:
            return move.value

    def hash_code(self, key):
        # 這裡僅為說明問題,13 的選擇是固定的
        hash_val = key % 13
        return hash_val


# 原始資料
src_nums = [25, 78, 56, 32, 88, 26, 39, 82, 14]
# 哈希對象
hash_table = HashTable()
# 把資料添加到哈希表中
for n in src_nums:
    hash_table.put(n, n)
# 輸出哈希表中的首結點資料
for i in hash_table.table:
    if i is not None:
        print(i.value,end=" ")
print("\n-------------查詢-----------")
print(hash_table.get(26))
'''
輸出結果:
78 14 56 32 88 25 
-------------查詢-----------
26
'''
           

3.總結

哈希表

是一種進階資料結構,其存儲、查詢性能非常好,在不考慮哈希雜湊演算法和哈希沖突的時間複雜度情況下,哈希查找時間複雜度可以達到常量級,成為很多實際應用場景下的首選。

研究

哈希表

,着重點就是搞清楚

雜湊演算法

以及如何解決

哈希沖突

。在算法的世界時,沒有固定的模式,開發者可以根據自己的需要自行設計雜湊演算法。

繼續閱讀