在學習Python的時候有時候會想,爲何dict和set的查找速度這麼快,感受就像是事先知道要找的元素的位置同樣?在學完哈希表以後,這個問題也就夠被很好的解釋了。python
哈希表是一種根據關鍵碼(key)去尋找值(value)的數據映射結構,該結構經過把關鍵碼映射的位置(index)去尋找存放值的地方。 舉個例子,也就是像小時候咱們常常查的字典同樣,好比咱們要查找一個字 「一」(value),咱們先獲得它的拼音「yi」(key),而後就能夠在字典的查找目錄看到這個字在哪一頁(index),最後就獲得這個字的詳細信息。數組
咱們知道,數組的查找速度之因此是O(1)是由於數組裏面的元素都有一個下標,因此參考數組的下標,給每一個元素一種[邏輯下標]。咱們把獲得的邏輯下標稱爲「槽」。數據結構
邏輯下標的計算方法採用的是取模運算: h(key) = key % M函數
當給出的值取到的「邏輯下標」相同時,哈希衝突便產生了。這裏引用一下別人的圖學習
遇到這種狀況該如何解決呢?咱們首先可以想到的是既然衝突了,那能不可以把這些衝突的放進一個鏈表裏面呢?或者從新找過其餘地方呢?spa
load factor = 元素個數 / 哈希表大小, 當裝載因子超過0.8時,就要開闢新的空間並從新進行散列了。code
從新開闢空間並散列的操做過程就叫作重哈希cdn
下面給出實現哈希表的代碼blog
# 哈希表是用數組完成的
class Array(object):
def __init__(self, size=32, init=None):
self._size = size
self._items = [init] * 32 # 獲得一個空的數組
def __getitem__(self, index):
"""返回value"""
return self._items[index]
def __setitem__(self, index, value):
"""重置value"""
self._items[index] = value
def __len__(self):
return self._size
def clear(self, value=None):
for i in range(len(self._items)):
self._items[i] = value
def __iter__(self):
for item in self._items:
yield item
# 定義槽,傳入key和value
class Slot(object):
def __init__(self, key, value):
self.key = key
self.value = value
# 定義哈希表
class HashTable(object):
# 首先定義兩個全局變量
UNUSED = None
EMPTY = Slot(None, None)
def __init__(self):
self._table = Array(8, init=HashTable.UNUSED)
self.length = 0 # 已使用槽的數量
def __load_factor(self):
"""定義裝載因子"""
return self.length / float(len(self._table))
def __len__(self):
return self.length
def __hash__(self):
"""定義哈希函數"""
return abs(hash(key)) % len(self._table)
def _find_key(self, key):
"""根據給出的key,得到index"""
index = self.__hash__(key)
_len = len(self._table)
while self._table[index] is not HashTable.UNUSED: # 這個槽已經被使用過且爲空,則從新查找
if self._table[index] is HashTable.EMPTY:
index = (index * 5 + 1) % _len
continue
elif self._table[index].key == key:
return index
else:
index = (index * 5 + 1) % _len
return None
def _slot_can_insert(self, index):
"""判斷找到的槽是否可用"""
return (self._table[index] is HashTable.UNUSED or self._table[index] is HashTable.EMPTY)
def _find_slot_for_insert(self, key):
"""查找能夠用的槽"""
index = self.__hash__(key)
_len = len(self._table)
while not self._slot_can_insert(index):
index = (index * 5 + 1) % _len
return index
def __contains__(self, key):
index = self._find_key(key)
return index is not None
def add(self, value, key):
"""往哈希表裏添加數據"""
if key in self:
index = self._find_key(key)
self._table[index] = value
return False
else:
index = self._find_slot_for_insert(key)
self._table[index] = Slot(key, value)
self.length += 1
if self.__load_factor() > 0.8:
self.rehash()
return True
def _rehash(self):
"""重哈希"""
old_table = self._table
newsize = len(self._table) * 2
self._table = Array(newsize, HashTable.EMPTY)
self.length = 0
for slot in old_table:
if slot is not HashTable.UNUSED or slot is not HashTable.EMPTY:
index = self._find_slot_for_insert(slot.key)
self._table[index] = slot
self.length += 1
def get(self, key, default=None):
"""取值"""
index = self._find_key(key)
if index is None:
return default
else:
return self._table[index].value
def remove(self, key):
index = self._find_key(key)
if index is None:
return KeyError()
value = self._table[index]
self.length -= 1
self._table[index] = HashTable.EMPTY
return value
def __iter__(self):
for slot in self._table:
if slot not in (HashTable.UNUSED, HashTable.EMPTY):
yield slot
複製代碼
哈希表是很高效的數據結構,對於新手來說也比較難理解,我手寫了一遍代碼,而後再用電腦敲了一遍才基本瞭解。新手寫的,請見諒rem