1. 前言
哈希表
或稱為
散清單
,是一種常見的、使用頻率非常高的資料存儲方案。
哈希表
屬于抽象資料結構,需要開發者按
哈希表
資料結構的存儲要求進行
API
定制,對于大部分進階語言而言,都會提供已經實作好的、可直接使用的
API
,如
JAVA
中有
MAP
集合、
C++
中的
MAP
容器,
Python
中的字典……
使用者可以使用
API
中的方法完成對
哈希表
的增、删、改、查……一系列操作。
如何學習哈希表?
可以從
2
個角度開始:
- **使用者角度:**隻需要知道
是基于哈希表
、鍵
對存儲的解決方案,另需要熟悉不同計算機語言提供的基于值
資料結構的哈希表
實作,學會使用API
中的方法。API
- 開發者的角度:則需要知道
底層實作原理,以及實作過程中需要解決的各種問題。本文将站在開發者的角度,帶着大家一起探究哈希表
的世界。哈希
2. 哈希表
什麼是哈希表?
哈希表
是基于
鍵
、
值
對存儲的資料結構,底層一般采用的是
清單(數組)
。
大家都知道,基于
清單(數組)
的查詢速度非常快,時間複雜度是
O(1)
,常量級别的。
清單的底層存儲結構是連續的記憶體區域,隻要給定資料在清單(數組)中的位置,就能直接查詢到資料。理論上是這麼回事,但在實際操作過程,查詢資料的時間複雜度卻不一定是常量級别的。
如存儲下面的學生資訊,學生資訊包括學生的姓名和學号。在存儲學生資料時,如果把學号為
的學生存儲在清單
位置,學号為
1
的學生存儲在清單
1
位置……
這裡把學生的學号和清單的索引号進行關聯,查詢某一個學生時,知道了學生的學号也就知道了學生資料存儲在清單中的位置,可以認為查詢的時間複雜度為
O(1)
。
之是以可以達到常量級,是因為這裡有資訊關聯(學生學号關聯到資料的存儲位置)。
還有一點,學生的學号是公開資訊也是常用資訊,很容易擷取。
但是,不是存儲任何資料時,都可以找到與清單位置相關聯的資訊。比如存儲所有的英文單詞,不可能為每一個英文單詞編号,即使編号了,編号在這裡也僅僅是流水号,沒有資料含義的資料對于使用者來講是不友好,誰也無法記住哪個英文單詞對應哪個編号。
是以使用清單存儲英文單詞後需要詢時,因沒有單詞的存儲位置。還是需要使用如
線性
、
二分
……之類的查詢算法,這時的時間複雜度由使用的查詢算法的時間複雜度決定。
如果對上述存儲在清單的學生資訊進行了
插入
、
删除
……等操作,改變了資料原來的位置後,因破壞了學号與位置關聯資訊,再查詢時也隻能使用其它查詢算法,不可能達到常量級。
是否存在一種方案,能最大化地優化資料的存儲和查詢?
通過上述的分析,可以得出一個結論,要提高查詢的速度,得想辦法把
資料
與
位置
進行關聯。而
哈希表
的核心思想便是如此。
2.1 哈希函數
哈希表
引入了
關鍵字
概念,
關鍵字
可以認為是資料的别名。如上表,可以給每一個學生起一個别名,這個就是
關鍵字
。
Tip: 這裡的是姓名的
關鍵字
縮寫,
拼音
和
關鍵字
的關聯性較強,友善記憶和查詢。
資料
有了
關鍵字
後,再把
關鍵字
映射成清單中的一個有效位置,映射方法就是
哈希表
中最重要的概念
哈希函數
。
是一個橋梁,即關聯到真正資料又關聯到哈希表中的位置。
關鍵字
也可以是需要儲存的資料本身。
關鍵字
哈希函數
的功能:提供把
關鍵字
映射到
清單
中的位置算法,是
哈希表
存儲資料的核心所在。如下圖,示範
資料
、
哈希函數
、
哈希表
之間的關系,可以說
哈希函數
是資料進入
哈希表
的入口。
資料最終會存儲在清單中的哪一個位置,完全由
雜湊演算法
決定。
當需要查詢學生資料時,同樣需要調用
哈希函數
對
關鍵字
進行換算,計算出資料在清單中的位置後就能很容易查詢到資料。
如果忽視
哈希函數
的時間複雜度,基于
哈希表
的資料存儲和查詢時間複雜度是
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
示範圖上出現了一個很奇怪的現象,沒有看到
李連傑
的存儲資訊。
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, '張志忠', '成龍']
'''
貌似解決了沖突問題,其實不然,當試着設定清單的長度為
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
解決沖突的流程:
-
和78
的哈希值都是 。而因為26
在78
的前面,26
先占據哈希表的 位置。78
- 當存儲
時,隻能以 位置為起始位置,向後尋找空位置,因26
位置沒有被其它資料占據,最終儲存在哈希表的1
位置。1
- 當存儲數字
時,通過雜湊演算法計算,其哈希值是14
,本應該要儲存在哈希表中1
的位置,因1
位置已經被1
所占據,隻能向後尋找空位置,最終落腳在26
位置。2
線性探測法讓發生
哈希沖突
的資料儲存在其它資料的哈希位置,如果沖突的資料較多,則占據的本應該屬于其它資料的哈希位置也較多,這種現象稱為
哈希聚集
。
查詢流程:
以查詢資料
14
為例。
- 計算
的哈希值,得到值為14
,根據哈希值在哈希表中找到對應位置。1
- 檢視對應位置是否存在資料,如果不存在,宣告查詢失敗,如果存在,則需要提供資料比較方法。
- 因
位置的資料1
并不等于26
。于是,繼續向後搜尋,并逐一比較。14
- 最終可以得到結論
在哈希表的編号為14
的位置。2
是以,在查詢過程中,除了要提供哈希函數,還需要提供資料比較函數。
删除流程:
以删除數字
26
為例。
- 按上述的查詢流程找到數字
在哈希表中的位置26
。1
- 設定位置
為删除狀态,一定要标注此位置曾經儲存過資料,而不能設定為空狀态。為什麼?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 連結清單法
上面所述的沖突解決方案的核心思想是,當沖突發生後,在哈希表中再查找一個有效空位置。
這種方案的優勢是不會産生額外的存儲空間,但易産生資料聚集,會讓資料的存儲不均衡,并且會違背初衷,通過
關鍵字
計算出來的
哈希值
并不能準确描述資料正确位置。
連結清單法
應該是所有解決
哈希沖突
中較完美的方案。所謂
連結清單法
,指當發生
哈希沖突
後,以沖突位置為首結點建構一條連結清單,以連結清單方式儲存所有發生沖突的資料。如下圖所示:
連結清單方案解決沖突,無論在存儲、查詢、删除時都不會影響其它資料位置的
獨立性
和
唯一性
,且因連結清單的操作速度較快,對于哈希表的整體性能都有較好改善。
使用連結清單法時,哈希表中儲存的是連結清單的首結點。首結點可以儲存資料也可以不儲存資料。
編碼實作連結清單法:連結清單實作需要定義 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.總結
哈希表
是一種進階資料結構,其存儲、查詢性能非常好,在不考慮哈希雜湊演算法和哈希沖突的時間複雜度情況下,哈希查找時間複雜度可以達到常量級,成為很多實際應用場景下的首選。
研究
哈希表
,着重點就是搞清楚
雜湊演算法
以及如何解決
哈希沖突
。在算法的世界時,沒有固定的模式,開發者可以根據自己的需要自行設計雜湊演算法。