前言:
之前,我們先後學習了線性表、陣列、字串和樹,它們普遍都存在這樣的缺陷,那就是資料數值條件的查詢,都需要對全部資料或者部分資料進行遍歷。那麼,有沒有一種方法可以省去資料比較的過程,從而進一步提升數值條件查詢的效率呢?
答案當然是:有!這一課時我們就來介紹這樣一種高效率的查詢神器:雜湊表。
雜湊表名字源於 Hash,也可以叫作雜湊表。雜湊表是一種特殊的資料結構,它與陣列、連結串列以及樹等我們之前學過的資料結構相比,有很明顯的區別。
雜湊表是一種資料結構,它使用雜湊函數組織資料,以支援快速插入和搜尋。雜湊表的核心思想就是使用雜湊函數將鍵對映到儲存桶。更確切地說:
下面舉一個簡單的例子,我們來理解下:
在範例中,我們使用 y = x % 5 作為雜湊函數。讓我們使用這個例子來完成插入和搜尋策略:
雜湊函數是雜湊表中最重要的元件,該雜湊表用於將鍵對映到特定的桶。在之前的範例中,我們使用 y = x % 5 作為雜湊函數,其中 x 是鍵值,y 是分配的桶的索引。
雜湊函數將取決於鍵值的範圍和桶的數量。下面是一些雜湊函數的範例:
雜湊函數的設計是一個開放的問題。其思想是儘可能將鍵分配到桶中,理想情況下,完美的雜湊函數將是鍵和桶之間的一對一對映。然而,在大多數情況下,雜湊函數並不完美,它需要在桶的數量和桶的容量之間進行權衡。
當然,我們也可以自定義一些雜湊函數。一般的方法有:
理想情況下,如果我們的雜湊函數是完美的一對一對映,我們將不需要處理衝突。不幸的是,在大多數情況下,衝突幾乎是不可避免的。例如,在我們之前的雜湊函數(y = x % 5)中,1987 和 2 都分配給了桶 2,這就是一個雜湊衝突。
解決雜湊衝突應該要思考以下幾個問題:
那麼一旦發生衝突,我們該如何解決呢?
常用的方法有兩種:開放定址法和鏈地址法。
即當一個關鍵字和另一個關鍵字發生衝突時,使用某種探測技術在雜湊表中形成一個探測序列,然後沿著這個探測序列依次查詢下去。當碰到一個空的單元時,則插入其中。
常用的探測方法是線性探測法。 比如有一組關鍵字 {12,13,25,23},採用的雜湊函數為 key % 11。當插入 12,13,25 時可以直接插入,地址分別為 1、2、3。而當插入 23 時,雜湊地址為 23 % 11 = 1。
然而,地址 1 已經被佔用,因此沿著地址 1 依次往下探測,直到探測到地址 4,發現為空,則將 23 插入其中。如下圖所示:
將雜湊地址相同的記錄儲存在一張線性連結串列中。例如,有一組關鍵字 {12,13,25,23,38,84,6,91,34},採用的雜湊函數為 key % 11。如下圖所示:
在很多高階語言中,雜湊函數、雜湊衝突都已經在底層完成了黑盒化處理,是不需要開發者自己設計的。也就是說,雜湊表完成了關鍵字到地址的對映,可以在常數級時間複雜度內通過關鍵字查詢到資料。
至於實現細節,比如用了哪個雜湊函數,用了什麼衝突處理,甚至某個資料記錄的雜湊地址是多少,都是不需要開發者關注的。接下來,我們從實際的開發角度,來看一下雜湊表對資料的增刪查操作。
雜湊表中的增加和刪除資料操作,不涉及增刪後對資料的挪移問題(陣列需要考慮),因此處理就可以了。
雜湊表查詢的細節過程是:對於給定的 key,通過雜湊函數計算雜湊地址 H (key)。
雖然雜湊表查詢的細節過程還比較麻煩,但因為一些高階語言的黑盒化處理,開發者並不需要實際去開發底層程式碼,只要呼叫相關的函數就可以了。
要求:
不使用任何內建的雜湊表庫設計一個雜湊對映具體地說,設計應該包含以下的功能:
- put(key, value):向雜湊對映中插入(鍵,值)的數值對。如果鍵對應的值已經存在,更新這個值。
- get(key):返回給定的鍵所對應的值,如果對映中不包含這個鍵,返回-1。
- remove(key):如果對映中存在這個鍵,刪除這個數值對。
範例:
MyHashMap hashMap = new MyHashMap();
hashMap.put(1, 1);
hashMap.put(2, 2);
hashMap.get(1); // 返回 1
hashMap.get(3); // 返回 -1 (未找到)
hashMap.put(2, 1); // 更新已有的值
hashMap.get(2); // 返回 1
hashMap.remove(2); // 刪除鍵為2的資料
hashMap.get(2); // 返回 -1 (未找到)
注意:
所有的值都在 [0, 1000000]的範圍內。
操作的總數目在[1, 10000]範圍內。
不要使用內建的雜湊庫。
雜湊表是一個在不同語言中都有的通用資料結構。例如,Python 中的 dict 、C++中的 map 和 Java 中的 Hashmap。雜湊表的特性是可以根據給出的 key 快速存取 value。
最簡單的思路就是用模運算作為雜湊方法,為了降低雜湊碰撞的概率,通常取素數的模,例如 模 2069。
定義 array 陣列作為儲存空間,通過雜湊方法計算陣列下標。為了解決 雜湊碰撞 (即鍵值不同,但對映下標相同),利用桶來儲存所有對應的數值。桶可以用陣列或連結串列來實現,在下面的具體實現中, Python 中用的是陣列。
定義雜湊表方法,get(),put() 和 remove(),其中的定址過程如下所示:
Python實現如下:
class Bucket:
def __init__(self):
self.bucket = []
def get(self, key):
for (k, v) in self.bucket:
if k == key:
return v
return -1
def update(self, key, value):
found = False
for i, kv in enumerate(self.bucket):
if key == kv[0]:
self.bucket[i] = (key, value)
found = True
break
if not found:
self.bucket.append((key, value))
def remove(self, key):
for i, kv in enumerate(self.bucket):
if key == kv[0]:
del self.bucket[i]
class MyHashMap(object):
def __init__(self):
"""
Initialize your data structure here.
"""
# better to be a prime number, less collision
self.key_space = 2069
self.hash_table = [Bucket() for i in range(self.key_space)]
def put(self, key, value):
"""
value will always be non-negative.
:type key: int
:type value: int
:rtype: None
"""
hash_key = key % self.key_space
self.hash_table[hash_key].update(key, value)
def get(self, key):
"""
Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key
:type key: int
:rtype: int
"""
hash_key = key % self.key_space
return self.hash_table[hash_key].get(key)
def remove(self, key):
"""
Removes the mapping of the specified value key if this map contains a mapping for the key
:type key: int
:rtype: None
"""
hash_key = key % self.key_space
self.hash_table[hash_key].remove(key)
# Your MyHashMap object will be instantiated and called as such:
# obj = MyHashMap()
# obj.put(key,value)
# param_2 = obj.get(key)
# obj.remove(key)
複雜度分析:
今天的分享就到這裡啦,希望對你的學習有所幫助!