散列函数的应用:实现映射抽象数据类型

散列函数的应用:实现映射抽象数据类型

1. 映射:键值对

字典是存储键–值对的数据类型。键用来查找关联的值,这个概念常常被称作映射。

映射抽象数据类型定义如下。它是将键和值关联起来的无序集合,其中的键是不重复的,键和值之间是一一对应的关系。

2. 代码清单

class HashTable:
    def __init__(self):
        self.size = 11 # 设置散列表的初始大小为11(最好为一个素数,尽可能提高冲突处理算法的效率)
        self.slots = [None] * self.size # 存储键
        self.data = [None] * self.size  # 存储值


    def put(self, key, data): # 往映射中加入一个新的键值对。如果键已经存在,则用新值替换旧值
        hashvalue = self.hashfunction(key, len(self.slots))

        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 = self.rehash(hashvalue, len(self.slots)) 
                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 # 替换
    
    def hashfunction(self, key, size): # 取余函数
        return (key % size)

    def rehash(self, oldhash, size): # 冲突处理算法:采用 +1 再散列函数 的线性探测法
        return ((oldhash + 1) % size)


    def get(self, key): # 返回key对应的值,若没有这个键则返回None
        startslot = self.hashfunction(key, len(self.slots))

        data = None
        stop = False
        found = False
        position = startslot

        while self.slots[position] != None and not found and not stop:
            if self.slots[position] == key:
                found = True
                data = self.data[position]
            else:
                position = self.rehash(position, len(self.slots))
                if position == startslot:
                    stop = True
        
        return data


    def __getitem__(self, key):
        return self.get(key)

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

3. 运行结果

>>> 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"
>>> H.slots
[77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
>>> H.data
['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
>>> H[20]
'chicken'
>>> H[20] = "duck"
>>> H[20]
'duck'
>>> H.data
['bird', 'goat', 'pig', 'duck', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
>>> print(H[99])
None

4. 散列搜索算法分析

在最好情况下,散列搜索算法的时间复杂度是 O ( 1 ) O(1) O(1) ,即常数阶。然而,因为可能发生冲突,所以比较次数通常不会这么简单。

在分析散列表的使用情况时,最重要的信息就是载荷因子 λ \lambda λ 。从概念上来说,如果 λ \lambda λ 很小,那么发生冲突的概率就很小,元素也就很有可能各就各位。如果 λ \lambda λ 很大,则意味着散列表很拥挤,发生冲突的概率也就很大。因此,冲突解决起来会更难,找到空槽所需的比较次数会更多。若采用链接法,冲突越多,每条链上的元素也越多。

和之前一样,来看看搜索成功和搜索失败的情况。采用线性探测策略的开放定址法,搜索成功的平均比较次数如下。

1 2 ( 1 + 1 1 − λ ) \frac{1}{2}(1 + \frac{1}{1 - \lambda}) 21(1+1λ1)

搜索失败的平均比较次数如下。

1 2 [ 1 + ( 1 1 − λ ) 2 ] \frac{1}{2}[1 + (\frac{1}{1 - \lambda})^2] 21[1+(1λ1)2]

若采用链接法,则搜索成功的平均比较次数如下。

1 + λ 2 1 + \frac{\lambda}{2} 1+2λ

搜索失败时,平均比较次数就是 λ \lambda λ

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值