“映射”抽象數據類型:ADT Map的python實現與散列衝突簡介

python最有用的數據類型之一爲“字典”,其是一種可以保存key-data鍵值對的數據類型。這種鍵值關聯的無序集合稱爲“映射Map”
映射特點:(1)關鍵碼具有唯一性; (2) 通過關鍵碼可以唯一確定一個數據值。
映射支持以下操作:

  • Map()創建一個空的映射,它返回一個空的映射集合。
  • put(key, val)往映射中加入一個新的鍵-值對。如果鍵已經存在,就用新值替換舊值。
  • get(key)返回key對應的值。如果可以不存在,則返回None。
  • del 通過del map[key]這樣的語句從映射中刪除鍵-值對。
  • len()返回映射中存儲的鍵-值對的數目。
  • in通過key in map這樣的語句,在鍵存在時返回True,否則返回False。

注意:字典的一大優勢是,給定一個鍵能很快地找到其關聯的值。爲了提高搜索效率,可以使用散列搜索算法,其時間複雜度可以達到O(1)。

映射的python實現:

##  映射數據類型的實現
# 使用兩個列表創建Hashtable類
class HashTable:
    def __init__(self):# HashTable類的構造方法
        self.size = 11# 散列表的初始大小設置爲素數,可以儘可能提高衝突處理算法的效率
        self.slots = [None] * self.size# 設置一個名爲slots的列表,用來存儲鍵,並初始化爲None
        self.data = [None] * self.size# 設置一個名爲data的列表,用來存儲值,並初始化爲None 
#實現一個名爲hashfunction的散列函數,要存儲或查找的鍵對散列表的大小進行取餘運算。
    def hashfunction(self, key, size):
        return key % size
    def rehash(self, oldhash, size):
        return (oldhash+1) % size# oldhash爲hashfunction()返回的值,但由於該位置上已經有了鍵,所以發生了衝突,需要使用rehash確定下一個位置
#3.實現put函數,首先使用hashfunction函數計算初始的散列值,
# 如果對應的位置上已有元素,就循環運行rehash函數,直到遇見一個空槽。如果槽中已有這個鍵,就用新值替換舊值。
    def put(self, key, data):
        hashvalue = self.hashfunction(key, len(self.slots))# 計算散列值,即key所對應的槽位
        if self.slots[hashvalue] == None:# 如果該槽位爲空,則在該位置添加鍵-值對
            self.slots[hashvalue] = key
            self.data[hashvalue] = data
        else:
            if self.slots[hashvalue] == key:  # 如果鍵已經存在
                self.data[hashvalue] = data  # 用新值替換舊值
            else:  # 該槽位被其他鍵佔用了,計算下一個散列值nextslot
                nextslot = self.rehash(hashvalue, len(self.slots))
                # 循環運行rehash直到遇見一個空槽,或發現鍵已經存在
                while self.slots[nextslot] != None and \
                        self.slots[nextslot] != key:
                    nextslot = self.rehash(nextslot, len(self.slots))
                if self.slots[nextslot] == None:  # 找到一個空槽位
                    self.slots[nextslot] = key
                    self.data[nextslot] = data
                else:  # 鍵已經存在
                    self.data[nextslot] = data
# 實現get函數,先計算初始散列值,如果值不在初始散列值對應的槽中,
# 就是用rehash確定下一個位置。如果遇到初始槽,就說明已經檢查完所有可能的槽,並且元素必定不存在。
    def get(self, key):
        startslot = self.hashfunction(key, len(self.slots))
        data = None
        stop = False# 一旦遇到初始槽,則賦值爲True,停止搜索
        found = False
        position = startslot、
        # 如果一開始初始槽就爲空,則說明元素必定不存在
        while self.slots[position] != None and \
                not found and not stop:
            if self.slots[position] == key:# 如果找到key
                found = True
                data = self.data[position]# 取出key所對應的值
            else:# 如果上一個position已經被其他鍵佔用,則計算下一個位置
                position = self.rehash(position, len(self.slots))
                if position == startslot:# 如果遇到初始槽,則搜索失敗,元素不存在
                    stop = True# 停止搜索
        return data# 返回data,如果搜素失敗,則返回None

    # 重載__getitem__和__setitem__, 以通過[]進行訪問
    def __getitem__(self, key):
        return self.get(key)

    def __setitem__(self, key, data):
        self.put(key, data)

##  測試
H = HashTable()
H[54] = 'cat'
H[26] = 'dog'
H[93] = 'lion'
H[17] = 'tiger'
H[77] = 'bird'
H[31] = 'cow'
H[44] = 'goat'
H[55] = 'pig'
H[20] = 'chicken'
print(H.slots)
print(H.data)
print(H[20])
print(H[17])
H[20] = 'duck'
print(H.data)
print(H[99])
##測試結果:
[77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
chicken
tiger
['bird', 'goat', 'pig', 'duck', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
None

算法分析:
散列在最好的情況下,可以提供常數級別O(1)的時間複雜度的查找性能。但可能存在散列衝突,查找次數可能就沒那麼簡單。
評估算列衝突的重要信息就是負載因子λ,其值較小,衝突的機率就小,較大是散列表填充較滿,衝突就會越來越多。

關於散列衝突的計算,較爲複雜,同時也是一個研究課題。限於篇幅,此處不做過多介紹。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章