目录
7.1哈希表(散列表)
7.1.1直接寻址法
举个例子:将nums=[1,3,5,4,6,1001],让数字和数组的下标对应,更加数组的下标就可以找对应的元素nums[1001]
优点:速度快,一次性找到结果
缺点:数据差异大的时候极大的浪费了空间
7.1.2除法哈希法
分析:让我们的目标树和长度计算余数,作为下标用来存储数据,可以让一个很小的数组存储非常大的数据.
举例:nums=[1,9,7,8,10],长度为5,存放的数据为:[10,1,7,8,9]
缺点:这时候如果我们将数字9改成数字6应该怎么存放尼?
这时候就出现了hash冲突(哈希碰撞),有两种解决方法.
7.1.3哈希碰撞
1.解决方法一:开发寻址法(不好)
如果出现了哈希碰撞,我们就将得到的数值存储到与它相邻的节点,如果相邻的节点也是满的,我们可以继续将它存储到下一个节点,一次类推,总能存储到,在进行查找的时候,我们可以对该数取余,再进行查找.
2.解决方法二:拉链法
在每个节点中我们不存放数据,我们存放链表,链表是变动的,链表的大小会随着存入数据的多少进行变动,我们可以将产生哈希碰撞的数据存放在链表中.
7.1.4评价
一个好的哈希算法可以使得程序的查找速度变得非常快,进行哈希映射之后,一个好的哈希算法能够让数据分布均匀,数据分布的越均匀,查询的速度越快
7.1.5代码实现
class HashTable:
def __init__(self,size):
self.size=size # 哈希表的长度
self.lis=[[] for i in range(5)] # 存放数据的列表,其实这里应该是链表,这里我们就用列表代替
# 对数据进行哈希运算
def hash(self,data):
return data%self.size
# 数据的查找
def find(self,data):
h=self.hash(data) # 对数据进行hash运算,得到数据应该存放的下标
lis=self.lis[h] # 获取数据存放对应的列表
if data in lis: # 如果这个数据在这个列表中,说明我们找到了,否则没有找到
return True
else:
return False
# 数据的存储
def put(self,data):
h=self.hash(data) # 对数据进行hash运算,得到数据应该存放的下标
lis=self.lis[h] # 获取数据存放对应的列表
if data not in lis: # 如果数据在对应的列表中不存在,我们可以将数据添加到该对应的列表中
lis.append(data)
if __name__ == '__main__':
h=HashTable(6)
h.put(1)
h.put(3)
h.put(7)
h.put(4)
h.put(8)
h.put(13)
print(h.lis)
7.2一致性hash
分布式缓存:
我们要将一个图片存放在S1,S2,S3台服务器上,为了分配均匀,我们使用:hash(图片名)%机器数=余数,对应的余数就是存放数据的编号
但是如果我们想要增加一台服务器该怎么办?如果我们增加一台服务器,那么对应的位置上的图片将会访问不到,就会造成缓存失效,进而导致服务器压力过大导致缓存穿透(缓存服务器内容不要充分利用,即使出现其中某台服务器瘫痪,其他服务器也能正常工作),因此我们应该使用一致性hash算法.
我们有2的32次方张图片,我们对服务器进行hash运算之后对其进行2的32次方取余得到结果,我们可以在hash环上多增加几台虚拟服务器例如服务器A我们可以映射几个虚拟服务器A0,A1,A2等,这样即使增加了服务器也不会对结果影响太多.具体请参看大佬视频
hash偏斜:
- 缓存分布不均匀---系统故障
- 最好让服务器尽量多--增加虚拟节点
- A----a1,a2,a3...
- 虚拟节点越多越均匀
- 缓存读写--虚拟节点--真实节点--读写
7.3局部敏感哈希(LSH)
7.3.1为什么要有局部敏感哈希?
如果只是要找到重复的微博,我们可以用两两比较所有的微博,对相同的微博值保留一条即可;但这只能在数据量很小的情况下才有可能,当我们有1000万条微博时,需要两两比较的微博有10^6亿(n*(n-1)/2)对,这个计算量是惊人的,即便你用map-reduce,拥有强大的集群,那也顶不住数据再增加一两个数量级。一种稍微好一点的办法是对所以微博进行一次hash,再对桶内的微博进行比较,利用hash可以过滤了绝大多数不相同的微博,避免了无谓的比较,时间复杂度O(n),显著提高效率。Hash的一个基本特性就是随机性,它将一个字符串随机的hash到一个桶中,对于相同的两个字符串,hash值总是相同的,但两个hash值只要相差一点,hash值就就可能大相径庭,可谓差之毫厘谬以千里
s1='hello gfb'
s2='hello gfb'
s3='hellogfb'
print(hash(s1),hash(s2),hash(s3))
如果能有一种hash算法,能将相似的字符串hash得到相似的hash值,那就能在接近线性的时间解决海量微博中的去重问题。幸好,这样的hash已经有大牛发明了,它叫局部敏感哈希
7.3.2文档相似度计算
度量距离的方式很多:欧式距离、编辑距离、余弦距离、Jaccard距离.距离和相似度是不同的概念,距离越近相似度应该越高,距离越远相似度应该越低,因此similar = 1-distace。
集合S和T的Jaccard相识度sim(s,t)=(s交t)/(s并t)当我们不考虑微博中重复出现的词时,一条微博就可以看成一个集合,集合的元素是一个个的词
s1 = '''从 决心 减肥 的 这 一刻 起 请 做 如下 小 改变 你 做 得 到 么'''
s2 = '''从 决心 减肥 的 这 一刻 起 请 做 如下 小 改变'''
sim(s1,s2)=11/16=0.69.
7.3.3文档shingling
{"从 决心","决心 减肥","减肥 的","的 这","这 一刻","一刻 起","起 请","请 做","做 如下","如下 小","小 改变"}
为了字面上相似的文档,将文档表示成集合最有效的方法是构建文档中的短字符串集合;k值的选取具有一定的技巧,k越大越能找到真正相似的文档,而k越小就能召回更多的文档,但他们可能相似度不高,比如k=1,就变成了基本词的比较了。我这里词作为shingle的基本单位,在英文处理中,是以字母为基本单位,原因在于汉字有上万个,而英文字母只有27个,以汉字为单位将造成shingle集合巨大。遍历所用文档,就得到了shingle全集。
7.3.4保持相似度矩阵表示
s1 = "我 减肥"
s2= "要"
s3 = "他 减肥 成功"
s4 = "我 要 减肥"
实际上,真正计算的过程中矩阵不是这样表示的,因为数据很稀疏。得到矩阵表示后,我们来看最小hash的定义。
7.4.5最小哈希
最小hash定义为:特征矩阵按行进行一个随机的排列后,第一个列值为1的行的行号。举例说明如下,假设之前的特征矩阵按行进行的一个随机排列如下:
参考文档: