python數據結構與算法——哈希表

哈希表 學習筆記

參考翻譯自:《複雜性思考》 及對應的online版本:http://greenteapress.com/complexity/html/thinkcomplexity004.htmlhtml

使用哈希表能夠進行很是快速的查找操做,查找時間爲常數,同時不須要元素排列有序python

python的內建數據類型:字典,就是用哈希表實現的數據結構

 

爲了解釋哈希表的工做原理,咱們來嘗試在不使用字典的狀況下實現哈希表結構。app

咱們須要定義一個包含 鍵->值 映射 的數據結構,同時實現如下兩種操做:函數

add(k, v):學習

  Add a new item that maps from key k to value v.測試

  With a Python dictionary,d, this operation is written d[k] = v.this

get(target):spa

  Look up and return the value that corresponds to key target.翻譯

  With a Python dictionary, d, this operation is written d[target] or d.get(target).

 

一種簡單是實現方法是創建一個線性表,使用元組來實現 key-value 的映射關係

 1 class LinearMap(object):
 2     """ 線性表結構 """
 3     def __init__(self):
 4         self.items = []
 5     
 6     def add(self, k, v):    # 往表中添加元素
 7         self.items.append((k,v))
 8     
 9     def get(self, k):       # 線性方式查找元素
10         for key, val in self.items:
11             if key==k:      # 鍵存在,返回值,不然拋出異常
12                 return val
13         raise KeyError

咱們能夠在使用add添加元素時讓items列表保持有序,而在使用get時採起二分查找方式,時間複雜度爲O(log n)。 然而往列表中插入一個新元素其實是一個線性操做,因此這種方法並不是最好的方法。同時,咱們仍然沒有達到常數查找時間的要求。

 

咱們能夠作如下改進,將總查詢表分割爲若干段較小的列表,好比100個子段。經過hash函數求出某個鍵的哈希值,再經過計算,獲得往哪一個子段中添加或查找。相對於從頭開始搜索列表,時間會極大的縮短。儘管get操做的增加依然是線性,但BetterMap類使得咱們離哈希表更近一步:

 1 class BetterMap(object):
 2     """ 利用LinearMap對象做爲子表,創建更快的查詢表 """
 3     def __init__(self,n=100):
 4         self.maps = []          # 總表格
 5         for i in range(n):      # 根據n的大小創建n個空的子表
 6             self.maps.append(LinearMap())
 7     
 8     def find_map(self,k):       # 經過hash函數計算索引值
 9         index = hash(k) % len(self.maps)
10         return self.maps[index] # 返回索引子表的引用     
11 
12     # 尋找合適的子表(linearMap對象),進行添加和查找
13     def add(self, k, v):
14         m = self.find_map(k)        
15         m.add(k,v)
16     
17     def get(self, k):
18         m = self.find_map(k)
19         return m.get(k)

測試一下:

 1 if __name__=="__main__":
 2     table = BetterMap()
 3     pricedata = [("Hohner257",257),
 4                  ("SW1664",280),
 5                  ("SCX64",1090),
 6                  ("SCX48",830),
 7                  ("Super64",2238),
 8                  ("CX12",1130),
 9                  ("Hohner270",620),
10                  ("F64C",9720),
11                  ("S48",1988)]
12     
13     for item, price in pricedata:
14         table.add(k=item, v=price)
15     
16     print table.get("CX12")
17     # >>> 1130
18     print table.get("QIMEI1248")
19     # >>> raise KeyError

因爲每一個鍵的hash值必然不一樣,因此對hash值取餘的值基本也是不一樣的。

當n=100時, BetterMap的查找速度大約是LinearMap的100倍。

 

明顯,BetterMap的查找速度受到參數n的限制,同時其中每一個LinearMap的長度不固定,使得子段中的元素依然是線性查找。若是,咱們可以限制每一個子段的最大長度,這樣在單個子段中查找的時間負責度就有一個固定上限,則LinearMap.get方法的時間複雜度就成爲了一個常數。由此,咱們僅僅須要跟蹤元素的數量,每當某個LinearMap中的元素數量超過閾值時, 對整個hashtable進行重排,同時增長更多的LinearMap,這樣子就能夠保證查找操做爲一個常數啦。

如下是hashtable的實現:

 1 class HashMap(object):
 2     def __init__(self):
 3         # 初始化總表爲,容量爲2的表格(含兩個子表)
 4         self.maps = BetterMap(2)
 5         self.num = 0        # 表中數據個數
 6     
 7     def get(self,k):        
 8         return self.maps.get(k)
 9     
10     def add(self, k, v):
11         # 若當前元素數量達到臨界值(子表總數)時,進行重排操做
12         # 對總表進行擴張,增長子表的個數爲當前元素個數的兩倍!
13         if self.num == len(self.maps.maps): 
14             self.resize()
15         
16         # 往重排事後的 self.map 添加新的元素
17         self.maps.add(k, v)
18         self.num += 1
19         
20     def resize(self):
21         """ 重排操做,添加新表, 注意重排鬚要線性的時間 """
22         # 先創建一個新的表,子表數 = 2 * 元素個數
23         new_maps = BetterMap(self.num * 2)
24         
25         for m in self.maps.maps:  # 檢索每一箇舊的子表
26             for k,v in m.items:   # 將子表的元素複製到新子表
27                 new_maps.add(k, v)
28         
29         self.maps = new_maps      # 令當前的表爲新表

重點關注 add 部分,該函數檢查元素個數與BetterMap的大小,若是相等,則「平均每一個LinearMap中的元素個數爲1」,而後調用resize方法。

resize建立一個新表,大小爲原來的兩倍,而後對舊錶中的元素「rehashes 再哈希」一 遍,放到新表中。

resize過程是線性的,聽起來好像很不怎麼好,由於咱們要求的hashtable具備常數時間。可是,要知道咱們並不須要常常進行重排操做,因此add操做在絕大部分時間中都是常數的,偶然出現線性。因爲對n個元素進行add操做的總時間與n成比例,因此每次add的平均時間就是一個常數!

假設咱們要添加32個元素,過程以下:

1. 因爲初始長度爲2,前兩次add不須要重排,第1,2次 總時間爲 2

2. 第3次add,重排爲4,耗時2,第3次時間爲 3

3. 第4次add,耗時1    到目前爲止,總時間爲 6

4. 第5次add,重排爲 8,耗時4,第5次時間爲5

5. 第6~8次   共耗時3      到目前爲止,總時間爲 6+5+3 = 14

6. 第9次add,重排16,  耗時8,第9次時間爲9

7. 第10~16次,共耗時7, 到目前爲止,總時間爲 14+9+7 = 30

在32次add後,總時間爲62的單位時間,由以上過程能夠發現一個規律,在n個元素add以後,當n爲2的冪,則當前總單位時間爲 2n-2,因此平均add時間絕對小於2單位時間。

當n爲2的冪時,爲最合適的數量,當n變大以後,平均時間爲稍微上升,但重要的是,咱們達到了O(1)。

相關文章
相關標籤/搜索