小白的hash复习计划
先上传一下相关资源吧,直接丢百度网盘了。
一份字典的操作整理,一份md文档,和下面的相同
还有一份是python数据结构黑书。
准备是看一下python数据结构,黑书yyds
第十章讲的是哈希表,结构:
- 讲解了py中字典的一些行为,参见dic_use.py(下面第二个大标题)
- 用list装了一个键值对的类,实现了一个collision库的有序字典(其实就是没哈希的)
- 哈希知识复习
- 根据MapBase基类实现了一个自己的哈希表(建议自己跟着书上码一遍,线性探测这种的实现还是很有意思的)
知识点复习
原理:
其实还是使用一个桶数组(也就是一系列的空间),我们通过存储内容的特点,计算出一个数据,将数据作为下标放在桶数组的对应位置。
效果:
我们能保证查找、插入的时候是O(1)的,因为按照key算出来数据直接存储即可。
(可能会有一些冲突的情况,另说)
特点:
一般我们都会剩余一些空间,有一个装填因子的概念(存储空间/总空间),也是因为当剩余空间很少的时候,冲突的可能性是很高的。
哈希方式:
整数作为哈希值,注意py中的哈希码是32位的,可能需要处理。
多形式哈希码:x0* an-1+x1* an-2 ……,我们只需要选择一个合适的常量a(一般是一些素数,比如33、39这样的)
循环移位哈希码:桶形移位,其实效果也不错
同时因为哈希的原因,我们的key要求是不可变类型,如果是类的实例,需要实现hash魔法函数。
压缩函数:
在得到一个哈希码之后,我们实际上的一个桶数组是由大小的,不能直接使用。
(比如一个哈希码为32位1,但是整个哈希表只有十个格子,很明显不能按照下标来进行分配)
- 划分方式:
说白了也就是映射,将i%N的值作为下标,N就是整个数组的大小 - MAD方式:
说白了是一个划分方式的加强版:[(a*i+b)%p]%N
使用一个质数p(p一般是大于总空间大小的一个质数),a、b则是[0,p)区间内的数据。
其实也就是将之前的冲突再一次打乱。
冲突处理:
前面提过一嘴,有一个冲突的问题。
因为我们的哈希码实际上是做不到一个数据一个的,像身份证号那样的唯一,只能尽量保证没有重复(这也是判断一个哈希函数好不好的判断标准)
- 分离链表
书上截个图吧,他这个图已经很形象了,就是冲突的节点就拉出来放置,都在同一个位置。
(这里的markdown没办法,不让贴图,书上275页图10-6) - 开放寻址
我们可以理解成一个垃圾堆,另外开辟一个空间,我们每一次冲突就将冲突的内容分配到新的空间中,在查找的时候,如果计算出哈希码的位置不是想要的,就在垃圾桶里面寻找。 - 线性探测及其变种
如果当前的位置被占据了,我们可以看一下旁边的位置,万一有不也是比较方便?
比如当前位置5被相同哈希码的内容占据,我们根据线性探测+1,在6的位置查看,然后是7、8……
那么在查找的时候,我们计算的位置和实际不相符,就需要在此位置上进行探测。
变种的话,二次探测就是使用+1,+4,+9这些平方数,或者是在冲突的基础上再使用一个哈希函数。
聚集问题:
如果是一个位置经常冲突,那么根据线性探测和二次探测,我们下一次填充就要比上一次填充远一个格,二次探测还好,线性直接全部堆在相邻的几个格子里面,很不方便。
二次哈希能解决这个问题。
py的实现方式还是很高端的,既然我们线性探测会有聚集问题,那么我们可以用随机数来决定偏移量啊。A[(h(k)+f(i))%N],其中f(i)为随机数。
这里肯定有人会问了,那随机数你后面怎么找? 别忘了,这里的随机数其实是伪随机数,当种子不变的时候,生成的数据是相同的。
字典相关操作
"""关于哈希表(字典的一些使用)"""
dic = {
0:'',
1:'x',
2:'xy',
3:'xyb',
4:'xybb'
}
"""通过key访问:需要getitem魔法函数实现"""
print(dic[3])
# 'xyb'
'''通过key替换:需要setitem实现
如果键不在字典中,就是添加'''
dic[3] = 'xyb '
print(dic[3])
# 'xyb '
'''通过键删除:需要delitem实现
如果键不在字典中,报错keyerror'''
del dic[0]
print(dic)
# {1: 'x', 2: 'xy', 3: 'xyb ', 4: 'xybb'}
'''返回字典的键值对个数,方法__len__实现'''
print(len(dic))
# 4
'''遍历,iter(dic)用的比较少,一般都是for key in dic,需要魔法函数__iter__'''
print(iter(dic))
# <dict_keyiterator object at 0x0000024ED11BC130>,可迭代对象
for key in dic:
print(key, dic[key])
# 1 x
# 2 xy
# 3 xyb
# 4 xybb
'''成员运算符in:判断一个键是否在dic中,返回Boolean型'''
if 5 in dic:
print('yes')
else:
print('no')
# no
'''get(k, d=None) k是键,d为查找失败的返回值,默认none并报错'''
print(dic.get(4))
# 'xybb'
print(dic.get(5))
# 这里按照书上说的是报错,但是亲测是返回一个none?
# 去查了一下,也没看到有说报错的,可能是书有问题
print(dic.get(5,None))
# 还是没报错
print(dic.get(5, 'what are you doing? f**k you!'))
# 返回d
'''dic.setdefault(k,d) 如果k in dic,返回dic[k],否则将dic[k]设置为d,并返回d'''
print(dic.setdefault(4,'xybb yyds'))
# 'xybb'
print(dic.setdefault(5, 'xybb yyds'))
# 'xybb yyds'
'''dic.pop(k, d=none) 弹出key=k的键值对,并返回对应的值,如果k不在,返回d,d=none报错'''
print(dic.pop(4))
# 'xybb'
print(dic.pop(6))
# 报错了
print(dic.pop(6,'yyds'))
# 'yyds'
print(dic)
# {1: 'x', 2: 'xy', 3: 'xyb ', 5: 'xybb yyds'},4没了
'''dic.popitem() 随机删除一对,返回一个元组,为空报错'''
print(dic.popitem())
# (5, 'xybb yyds')
print(dic)
# {1: 'x', 2: 'xy', 3: 'xyb '}, 5没了
print({}.popitem())
# 报错
'''dic.clear() 清空'''
dic.clear()
print(dic)
# {}
dic = {
0:'',
1:'x',
2:'xy',
3:'xyb',
4:'xybb'
}
'''keys() values() items() 键、值、键值对的可迭代对象'''
for key in dic.keys():
print(key)
# value item使用同理,不过items是返回一个元组,我们可以用解封装拆成k-v两个变量用
# 这部分是可迭代对象,一般来说打印了也不行
'''update,可以理解成列表的extend'''
dic.update({'1':1,'2':2})
print(dic)
# {0: '', 1: 'x', 2: 'xy', 3: 'xyb', 4: 'xybb', '1': 1, '2': 2}
'''也可以用==和!=判断相等'''
dic = {'xybb':'yyds'}
dic2 = {'xybb':'yyd'}
if dic==dic2:
print('yes')
else:
print('no')
# no