沙師弟學數據結構與算法之哈希表

哈希表

在學習Python的時候有時候會想,爲何dict和set的查找速度這麼快,感受就像是事先知道要找的元素的位置同樣?在學完哈希表以後,這個問題也就夠被很好的解釋了。python

定義

哈希表是一種根據關鍵碼(key)去尋找(value)的數據映射結構,該結構經過把關鍵碼映射的位置(index)去尋找存放值的地方。 舉個例子,也就是像小時候咱們常常查的字典同樣,好比咱們要查找一個字 「一」(value),咱們先獲得它的拼音「yi」(key),而後就能夠在字典的查找目錄看到這個字在哪一頁(index),最後就獲得這個字的詳細信息。數組

實現方法

咱們知道,數組的查找速度之因此是O(1)是由於數組裏面的元素都有一個下標,因此參考數組的下標,給每一個元素一種[邏輯下標]。咱們把獲得的邏輯下標稱爲「」。數據結構

邏輯下標的計算方法採用的是取模運算: h(key) = key % M函數

哈希衝突

當給出的值取到的「邏輯下標」相同時,哈希衝突便產生了。這裏引用一下別人的圖學習

解決辦法

遇到這種狀況該如何解決呢?咱們首先可以想到的是既然衝突了,那能不可以把這些衝突的放進一個鏈表裏面呢?或者從新找過其餘地方呢?spa

  1. 讓數組中 衝突的槽 變成一個鏈式結構,可是這個方法有個缺點,當鏈表太長的時候,就會形成時間退化,查找時間會從O(1)退化爲O(n),所以這種方法比較少用。
  2. 尋找下一個槽,這裏給出的是比較經常使用的二次探查(以二次方做爲偏移量)

裝載因子(load factor)

load factor = 元素個數 / 哈希表大小, 當裝載因子超過0.8時,就要開闢新的空間並從新進行散列了。code

重哈希(Rehashing)

從新開闢空間並散列的操做過程就叫作重哈希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

相關文章
相關標籤/搜索