数据结构之哈希表/散列表——python应用(字典)

1. 什么是哈希表?

  • 散列函数:一个把查找表中的关键字映射成该关键字对应的地址的函数,记为: Hash(key)=Addr
  • 哈希表:是根据关键字而直接进行访问值的数据结构。也就是说散列表建立了关键字和存储地址之间的一种直接映射关系。

2. 哈希表为何如此高效?

  • 回顾已经学过的数据结构的查找,例如数组,链表等,他们的查找都是建立在“比较”的基础上的,时间复杂度通常为O(n)
  • 而哈希表直接通过给定Key值,获取到地址,从该地址中取出数据,理想情况下时间复杂度只有O(1),即与表中元素个数无关。

3. 哈希表的冲突现象

3.1 冲突现象

  • 哈希函数可能会把两个或者两个以上的不同关键字映射到同一地址,这种情况被称为“冲突”,这些发生碰撞的不同关键字称为同义词

3.2 如何解决哈希冲突现象

  • 在未建立哈希表前选择好的哈希函数来尽量减少这样的冲突
  • 在建立哈希表后,可以通过开放地址法或者拉链法来减少冲突

4. 哈希函数的构造方法

4.1 构造哈希函数的准则

  • 哈希函数的定义域必须包含所有的需要存储的关键字,而值域的范围依赖于散列表的大小或者地址范围
  • 哈希函数计算出来的地址应尽量等概率的均匀分布在整个地址空间。
  • 散列函数应该尽可能简单。

4.2 构造方法

4.2.1 直接定址法

  • 直接取关键字的某个线性函数值为散列地址,散列函数为:
    在这里插入图片描述
    式中a,b为常数。这种计算方法最简单,并且不会产生冲突,适合关键字分布基本连续的情况。若关键字分布不连续,空位较多,将造成存储空间的浪费。

4.2.2 除留余数法

  • 最常用的方法,假定哈希表的表长为m,取一个不大于m但最接近或者等于m的质数p,利用下面的公式将关键字转换成哈希地址:
    在这里插入图片描述
    除留余数法关键是选好p,使得每一个关键字通过该函数转换后等概率地映射到哈希空间上,从而尽可能地减少冲突现象。

5. 处理冲突地方法

  • 注意到任何设计出来地哈希函数都不能绝对地避免冲突,为此需要考虑在发生冲突后应该如何处理,即为“冲突”找到下一个空地Hash地址。
  • 假定已经选好了哈希函数:H(key),下面用Hi表示发生冲突后第i次探测哈希地址。

5.1 开放地址法

  • 开发地址法指的是,可存放新表项地空闲地址既向他的同义词表开放,也想非同义词表开放。其数学递推公式如下,其中m为表长,i=0,1,2,3…,m-1。di为增量序列。当取定某一增量后,则对应地处理方法是固定地。

在这里插入图片描述

5.1.1 线性探测法

  • 当di=0,1,2,3…,m-1时,称为线性探测法。
  • 方式:冲突发生后,顺序查看下一个单元是否是空的(当探测到表尾地址m-1时,下一个探测地址时表首地址0),直到找到一个空闲单元。如果查找失败,则表一定是满的。
  • 缺点:线性探测法可能使第i个哈希地址地同义词存入第i+1个哈希地址,这样原本存在第i+1个哈希值智能存放在第i+2个,以此类推,这样会大大降低查找速率

5.1.2 平方探测法

  • 当di=square(0),square(1),-square(1),sauare(2),-square(2)…,square(k),-square(k)
  • 方式:和上面差不多,只不过换了di
  • 缺点:不能探测到表上地所有元素
  • 注意:k<=m/2且m必须可以表示成4K+3地一个素数

5.2 拉链法

在这里插入图片描述

6. 决定哈希表查找效率地因素

  • 哈希函数地选取
  • 哈希冲突地解决方法
  • 装填因子α
    在这里插入图片描述

7. 哈希表在python中的应用

Python中的字典便应用了哈希表的原理,能使关键字和值一一对应。

  • 字典的初始化:
a = dict()
b = {}
c = {'a': 1, 'b': 2, 'b': '3'}  # 冒号左边是key,右边是value,由于存在重复的b,最后剩下右边的'b':3
  • 访问字典中的value
print ("c['a']: ", c['a'])
  • 修改字典
c['a'] = 0 # 将原来映射的1变为0
c['c'] = 4 # 添加新的键值对
  • 删除字典元素
del c['a'] #删除key为a的键值对
c.clear #清除字典c中所有的键值对
del c #直接删除字典c
  • 字典内置函数和方法
    在这里插入图片描述
    在这里插入图片描述

8. LeetCode中的应用

  1. 两数之和(twoSum)
class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        map_a = dict()
        k = len(nums)
        for i in range(0, k):   #一边将列表中的数添加到字典中,一边判断两数之差是否存在于字典中
            temp = target - nums[i]  
            if temp in map_a :  # 判断步骤
                return [map_a[temp], i]
            map_a[nums[i]] =  i  # 添加步骤(切记先判断再添加,以免key冲突)

在这里插入图片描述
2.快乐数

class Solution(object):
    def isHappy(self, n):
        """
        :type n: int
        :rtype: bool
        """

        dict_1 = {}

        while 1:
            sum = 0

            while n != 0:   #计算平方之和
                sum += (n % 10) ** 2
                n /= 10

            n = sum

            if n == 1:   #如果平方之和为1,证明n是快乐数,返回True
                return True
            elif dict_1.has_key(n):  #若平方之和已出现过(循环计算),证明加法之和不可能为1,n不是快乐数,返回False
                return False

            dict_1[n] = 0

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

InceptionZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值