Python数据结构与算法57:排序与查找:ADT Map

:本文如涉及到代码,均经过Python 3.7实际运行检验,保证其严谨性。

本文阅读时间约为6分钟

映射抽象数据类型及Python实现

在Python字典中,我们可以通过“键”访问对应的“值”。字典这种键值关联的方法称为映射(map)。

字典就是这样一种抽象数据类型映射(ADT Map)结构。ADT Map结构是键-值关联的集合。在这个集合中,关键码具有唯一性,我们可以通过关键码唯一确定一个数据值。

抽象数据类型映射(ADT Map)定义的操作如下:

  • Map()——创建一个空映射,返回空映射对象。
  • put(key, val)——将key-val关联对加入到映射中。若映射中key已存在,则val将替换key的旧关联值。
  • get(key)——给定key,返回关联的数据值。如key不存在,则返回None.
  • del——通过del map[key]的语句形式删除key-val关联。
  • len()——返回映射中key-val关联的数目。
  • in——通过key in map的语句形式,返回key是否存在于关联中,布尔值。

字典的优势在于,给定关键码key,能够很快找到关联的数据值data。
为了达到快速查找的目标,需要一个支持高效查找的ADT(Abstract Data Type,抽象数据类型)实现——可以采用列表数据结构加顺序查找或者二分查找,当然,更为合适的是,使用散列表来实现,因可以达到最快O(1)的性能。

实现抽象数据类型映射

下面,我们用一个Hash Table类来实现ADT Map,该类包含了两个列表作为成员:一个是slot列表,用于保存key;另一个是平行的data列表,用于保存数据项。

在slot列表查找到一个key的位置以后,在data列表对应的相对位置的数据项即为关联数据。

保存key的列表就作为散列表来处理,这样可以迅速查找指定的key。

注意散列表的大小,虽然可以是任意数,但考虑到要让冲突解决算法能有效工作,应该选择为素数。

hashfunction方法采用了简单求余方法来实现散列函数,而冲突解决则采用线性探测“加1”再散列函数。

综上所述,ADT Map的put、get等方法的实现的完全代码如下:

# 实现抽象数据类型映射的实现。
class HashTable:
    def __init__(self):
        self.size = 11
        self.slots =  [None] * self.size
        self.data = [None] * self.size
    
    # ADT Map的put方法。
    def put(self, key, data):
        hashvalue = self.hashfunction(key)
        
        if self.slots[hashvalue] == None:  # key不存在的情况。
            self.slots[hashvalue] = key
            self.data[hashvalue] = data
        else:
            if self.slots[hashvalue] == key:  # key存在的情况。
                self.data[hashvalue] = data  # 替换。
            else:
                nextslot = self.rehash(hashvalue)
                
                # 散列冲突,再散列,直到找到空槽或者key。
                while self.slots[nextslot] != None and self.slots[nextslot] != key:
                    nextslot = self.rehash(nextslot)
                
                if self.slots[nextslot] == None:
                    self.slots[nextslot] = key
                    self.data[nextslot] = data
                else:
                    self.data[nextslot] = data  # 替换。
                    
    def hashfunction(self, key):
        return key % self.size
    
    def rehash(self, oldhash):
        return (oldhash + 1) % self.size
    
    # ADT Map的get方法。
    def get(self, key):
        startslot = self.hashfunction(key)  # 标记散列值为查找起点。
        
        data = None
        stop = False
        found = False
        position =  startslot
        while self.slots[position] != None and not found and not stop:
        # 找到key,直到空槽或者回到起点。
            if self.slots[position] == key:
                found = True
                data = self.data[position]
            else:  #未找到key,再散列继续找。
                position = self.rehash(position)
                if position == startslot:
                    stop = True  # 没找到key,回到起点,停下来了。
        return data
    
    # 通过特殊方法实现[]访问。
    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])

<<<
[77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
chicken
<<<
散列的算法分析

散列在最好的情况下,可以提供O(1)即常数级的时间复杂度的查找性能。当然,因为实际情况中不免有散列冲突的存在,所以查找比较次数就没这么简单。

评估散列冲突的最重要信息就是负载因子λ,一般来说:

  • 如果λ较小,散列冲突的几率就小,数据项通常会保存在其所属的散列槽中;
  • 如果λ较大,意味着散列表填充较满,冲突会越来越多,冲突解决也越来越复杂,也就需要更多的比较来找到空槽;如果采用数据项链的方式,则意味着每条链上的数据项增多。

如果采用线性探测的开放定址法来解决冲突问题(λ在0~1之间),则:

  • 成功的查找,平均需要比对次数为:(1+1/(1-λ))/2。
  • 不成功的查找,平均比对次数为:(1+(1/(1-λ))^2)/2。

如果采用数据项链法来解决冲突问题(λ可大于1),则:

  • 成功的查找,平均需要比对次数为:1+λ/2。
  • 不成功的查找,平均比对次数为:λ。

To be continued.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容