将每一项存在数组中,经过下标来索引。这种实现的方式问题在于:算法
解决方案:将key从string映射成int数组
解决方案:将全部可能的key映射到一个大小为m的table中,理想状况 m=n,n表示table中key的个数。问题:有可能形成冲突,即两个不一样的key计算hash以后,却获得了同一个keybash
使用hash函数。数据结构
h(k)=k mod mapp
这种方式选择的m一般是与2的幂次方不太接近的质数ide
其中a是个随机数,k包含w个bit,m通常选择 函数
取值规则以下: ui
h(k)=[(ak+b)mod p]mod m
其中a,b是{0,..,p-1}中的随机值,P是一个大的质数spa
若是key是同样的,就在table的当前索引值以后加一个链表,指向新的加入的值,此时,最坏的状况就是,全部的key都hash冲突,致使最坏的查找时间为O(n).net
简单一致hash
假设每一个key被映射到table中的任意一个索引的几率是同样的,与其它的key经过hashing计算出来的位置无关。
在这种假设下 ,假设一共有n个key,表的大小为m,那么每一个链条的长度![]()
那么通常状况下,运行时间为 O(1+α),于是能够看到在假设的前提之下,使用链表解决hash冲突是个不错的选择
具体策略为,hash函数包括要计算hash的key和尝试的次数来获得具体的下标
假设通过3次插入数据,h(586,1)=1,h(133,1)=2,h(112,1)=4
再次插入一个数据h(226,1)=4,此时产生了冲突,增长重试的次数,获得h(226,2)=3此时尚未存储值,能够插入 ![]()
标记的方式用于解决,示例中的,加入删除了112,在查找226的过程当中,计算h(226,1)==4,而以前的位置被112占据,若是删除112的时候置为空,那么此时会标记为找不到,很明显不正确,若是仅标记为已经删除则能够解决这个问题,对于带有删除标记的位置,一样能够插入,这样就解决了问题
这种状况下,须要尝试的次数为
指望查找的时间是常量,那么但愿,考虑到m过小,查询慢;m太大,浪费
给定两个字符串s和t,须要判断s是否在t中出现。
最简单的方法是两次遍历:
for i in range(len(t)-len(s)):
for j in range(len(s)):
依次对比是否可以成功匹配
复制代码
它的执行规则为遍历整个的字符串,而后依次去匹配短字符串s是否存在原来的数组中,没有找到,依次后移
使用Karp-Rabin算法提升速度,对于要匹配的字符串s,能够直接算出它的hash值,对于字符串t,须要首选获取一个长度为|s|的字符串,一样能够计算它的hash值
若是在上面的计算过程都可以在常量时间内完成,那么总共的花销为O(|t|)。具体实施以下:
def rhCombinationMatch(self):
winLength = len(self.findStr)
//构建要查找的字符串RollingHash对象
winRh = RollingHashCombination(self.findStr)
lineLen = len(self.lines)
//构建要屡次计算的字符串的RollingHash对象
matchRh = RollingHashCombination(self.lines[0:winLength])
for i in range(0,lineLen-winLength+1):
//判断两个的hash值是否一致
if matchRh.hash() == winRh.hash():
sequence=self.lines[i:i+winLength]
# 若是一致,排除hash冲突的影响,看下字符串是否相等
if sequence == self.findStr:
self.count+=1
if i+winLength<lineLen:
//没有匹配到,变换新的字符串,去掉第一个,加上下一个
matchRh.slide(self.lines[i],self.lines[i+winLength])
复制代码
构建的RollingHash对象以下,它主要负责去将每个步骤组装起来
class RollingHashCombination(object):
""" 将rolling hash的每一步组合起来 """
def __init__(self,s):
base = 7
p=499999
self.rhStepByStep = RollingHashStepByStep(base,p)
for c in s:
self.rhStepByStep.append(c)
self.chash = self.rhStepByStep.hash()
def hash(self):
return self.chash
//依次删掉以前的值 , 添加新的值
def slide(self,preChar,nextChar):
""" 删掉以前的值 , 添加新的值 """
self.rhStepByStep.skip(preChar)
self.rhStepByStep.append(nextChar)
self.chash = self.rhStepByStep.hash()
复制代码
举例假设有5个字符串为"ABCDEF",要找的字符串长度为3,而hash值仅根据ASCII来直接拼接,真整个计算过程匹配以下:
于是原始的字符从656667演变成了666768。假设
那么n(1) = (n(0)-old*base^(K-1))*base+new,假设旧数字的hash值是 h1,新数字的hash是
h2=[(n(0)-old*base^(K-1))*base+new] mod p
=[(n(0) mode p)*base -old *(base^(k) mod p) +new ] mod p //对同行一个数求两次余数不会改变结果
复制代码
使magic = base(k) mod p 而 h1 = n(0) mod p,h2= [h1base -oldmagic +new ]mod p
代码实现以下,负责每一个步骤hash值的计算
class RollingHashStepByStep(object):
""" 对RollingHash进行一步一步的拆分,能够分红两个步骤,每一个步骤都会生成对应的hash值 """
def __init__(self, base,p):
""" 获得一个rollinghash初始值 """
super(RollingHashStepByStep, self).__init__()
self.base = base
# 质数
self.p = p
# 刚开始没有元素
self.chash= 0
# 刚开始没有元素 magic = magic ** k %p k=0
self.magic= 1
self.ibase = base ** (-1)
# 保证数据小
def append(self,newChar):
""" 在原有的hash基础上增长一个字符,计算其hash值 """
# old 返回一个字串的 ASCII值
new10=ord(newChar)
self.chash = (self.chash * self.base + new10 ) % self.p
#滑动窗口中增长一个元素,根据magic的定义 magic是base的长度的次方
self.magic = (self.magic * self.base)
def skip(self,oldChar):
""" 在原有的hash基础上去掉一个字符,计算其hash值 """
# hash-old*magic 多是负值 old < base magic <p
self.magic =int(self.magic * self.ibase)
# todo 进制计算,为何传进来的数字不须要转换成对应的进制 在不用base的地方进行解答
old10 =ord(oldChar);
self.chash = (self.chash-old10*self.magic + self.p * self.base ) % self.p
def hash(self):
return self.chash
复制代码