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

1. 前言

哈希表或稱為散列表,是一種常見的、使用頻率非常高的數據存儲方案。

哈希表屬於抽象數據結構,需要開發者按哈希表數據結構的存儲要求進行 API 訂製,對於大部分高級語言而言,都會提供已經實現好的、可直接使用的 API,如 JAVA 中有 MAP 集合、C++ 中的 MAP 容器,Python 中的字典……

使用者可以使用 API 中的方法完成對哈希表的增、刪、改、查……一系列操作。

如何學習哈希表?

可以從 2 個角度開始:

  • 使用者角度:只需要知道哈希表是基於對存儲的解決方案,另需要熟悉不同電腦語言提供的基於哈希表數據結構的 API實現,學會使用 API中的方法。
  • 開發者的角度:則需要知道哈希表底層實現原理,以及實現過程中需要解決的各種問題。本文將站在開發者的角度,帶著大家一起探究哈希的世界。

2. 哈希表

什麼是哈希表?

哈希表是基於對存儲的數據結構,底層一般採用的是列表(數組)

大家都知道,基於列表(數組)的查詢速度非常快,時間複雜度是 O(1),常量級別的。

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

如存儲下面的學生資訊,學生資訊包括學生的姓名和學號。在存儲學生數據時,如果把學號為 0 的學生存儲在列表 0 位置,學號為 1 的學生存儲在列表 1 位置……

hx01.png

這裡把學生的學號和列表的索引號進行關聯,查詢某一個學生時,知道了學生的學號也就知道了學生數據存儲在列表中的位置,可以認為查詢的時間複雜度為 O(1)

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

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

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

所以使用列表存儲英文單詞後需要詢時,因沒有單詞的存儲位置。還是需要使用如線性二分……之類的查詢演算法,這時的時間複雜度由使用的查詢演算法的時間複雜度決定。

如果對上述存儲在列表的學生資訊進行了插入刪除……等操作,改變了數據原來的位置後,因破壞了學號與位置關聯資訊,再查詢時也只能使用其它查詢演算法,不可能達到常量級。

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

通過上述的分析,可以得出一個結論,要提高查詢的速度,得想辦法把數據位置進行關聯。而哈希表的核心思想便是如此。

2.1 哈希函數

哈希表引入了關鍵字概念,關鍵字可以認為是數據的別名。如上表,可以給每一個學生起一個別名,這個就是關鍵字

hx02.png

Tip: 這裡的關鍵字是姓名的拼音縮寫,關鍵字數據的關聯性較強,方便記憶和查詢。

有了關鍵字後,再把關鍵字映射成列表中的一個有效位置,映射方法就是哈希表中最重要的概念哈希函數

關鍵字是一個橋樑,即關聯到真正數據又關聯到哈希表中的位置。

關鍵字也可以是需要保存的數據本身。

哈希函數的功能:提供把關鍵字映射到列表中的位置演算法,是哈希表存儲數據的核心所在。如下圖,演示數據哈希函數哈希表之間的關係,可以說哈希函數是數據進入哈希表的入口。

hx03.png

數據最終會存儲在列表中的哪一個位置,完全由哈希演算法決定。

當需要查詢學生數據時,同樣需要調用哈希函數關鍵字進行換算,計算出數據在列表中的位置後就能很容易查詢到數據。

如果忽視哈希函數的時間複雜度,基於哈希表的數據存儲和查詢時間複雜度是 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)(//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=4848 除以 4 取餘數,結果是0
  • llj的哈希值為 12+12+10=3434 除以 4 取餘數,結果是2
  • cl 的哈希值為 3+12=1515 除以 4 取餘數,結果是3
  • zzz的哈希值為 26+26+26=7878 除以 4 取餘數,結果是2

hx05.png

演示圖上出現了一個很奇怪的現象,沒有看到李連杰的存儲資訊。

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, '張志忠', '成龍']
'''

hx06.png

貌似解決了衝突問題,其實不然,當試著設置列表的長度為678910時,只有當長度為 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
'''

間界疊加法:

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

如訂單編號 199811123位一組分割,分割後的結果: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

hx07.png

解決衝突的流程:

  1. 7826的哈希值都是 0。而因為7826的前面,78先佔據哈希表的 0位置。

  2. 當存儲 26時,只能以 0位置為起始位置,向後尋找空位置,因 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:為了保證當哈希值發生衝突後,如果從衝突位置查到哈希表的結束位置還是沒有找到空位置,則再從哈希表的起始位置,也就是 0 位置再搜索到衝突位置。衝突位置是起點也是終點,構建一個查找邏輯環,以保證一定能找到空位置。

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 鏈表法

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

這種方案的優勢是不會產生額外的存儲空間,但易產生數據聚集,會讓數據的存儲不均衡,並且會違背初衷,通過關鍵字計算出來的哈希值並不能準確描述數據正確位置。

鏈表法應該是所有解決哈希衝突中較完美的方案。所謂鏈表法,指當發生哈希衝突後,以衝突位置為首結點構建一條鏈表,以鏈表方式保存所有發生衝突的數據。如下圖所示:

hx08.png

鏈表方案解決衝突,無論在存儲、查詢、刪除時都不會影響其它數據位置的獨立性唯一性,且因鏈表的操作速度較快,對於哈希表的整體性能都有較好改善。

使用鏈表法時,哈希表中保存的是鏈表的首結點。首結點可以保存數據也可以不保存數據。

編碼實現鏈表法:鏈表實現需要定義 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.總結

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

研究哈希表,著重點就是搞清楚哈希演算法以及如何解決哈希衝突。在演算法的世界時,沒有固定的模式,開發者可以根據自己的需要自行設計哈希演算法。