我表示看完这两个匹配算法,头顶有点凉…
BM算法与KMP算法的主要区别在于:采用从右向左进行字符的匹配比较。
BM算法中的关键问题是, 如何确定目标串中的下一轮匹配的开始位置?即如何决定目标串中指针向右跳跃的距离?——利用P中的重复模式 和 T中的失配字符
假设出现失配时, T[i]≠P[k]。则 此时坏字符为x(=T[i]),好后缀P’=P[(k+1) … (len(P)-1)](好后缀,是已匹配的部分字符串)
如何确定目标串中查找指针的移动距离dist[i]?
采用2种启发式方法:无需检查目标串中的所有字符即可查找到是否存在匹配子串。
- 启发式方法#1: 跳过字符(“坏字符”规则)
- CharJump[x]:依据T中的坏字符x,计算T中查找指针的跳跃距离
- 启发式方法#2: 重复模式(“好后缀”规则)
- MatchJump[k] : 依据P中的失配位置k, 计算T中查找指针的跳跃距离
基本步骤
Step1:对于模式串P,计算CharJump[x]和MatchJump[k]。
Step2: 将T与P的第一个字符对齐。
Step3: T与P进行从右向左的逐字符比较 ,直至找到一个不匹配字符或者P中所有字符都匹配成功。
Step4: 若出现失配,即存在T[i]≠P[k],此时坏字符x=T[i],好后缀P’=P[(k+1) … (len(P)-1)]。按如下规则计算 目标串T中查找指针向右移动dist[i]:
- 若此时T与P已有部分字符匹配(即存在“好后缀” ) 时, BM算法将采用2种启发式方法(即坏字符规则 和好后缀规则 ) ,计算dist[i]=max(CharJump[x],MatchJump[k])。
- 若不存在“好后缀”,则必定是在模式串P的最后一个字符处出现失配。此时应采用启发式方法#1: 跳过字符规则(“好后缀”规则),计算设置dist[i] =CharJump[x]。
Step5: 若(i+dist[i])≤Len(T)-1,则移动模式字符串P,使之与T[i+dist[i]]右对齐,重复Step3;否则,认为T中不存在与P匹配的子串,返回匹配失败。
坏字符的两种情况
- 如果模式串中没有出现坏字符,那么从字符x开始的Len(P)个字符显然不可能与P匹配成功, 使目标串中查找指针直接跳过Len(P) 个字符。 即将模式串整体挪到该字符之后
- 如果坏字符x在模式P中出现(假设P[j]==x),则将目标串中查找指针移动CharJump[x],使得下一轮匹配中将字符P[j]与坏字符x进行对齐。即该坏字符与模式串中最后出现该字符的位置对齐——因为是从右向左匹配
- 关键问题是计算CharJump[x]
- 若x在P中出现,假设p[j]==x,则CharJump[x]=Len(P)-max(j)-1 —— max计算最后位置
- 否则, CharJump[x]=Len(P);
- 关键问题是计算CharJump[x]
注意: 模式串P右移距离shift=dist[i]-Len(u)= CharJump[x]- (Len(P)-1-k) 个字符。
在第二种情况下,无法保证模式串一定是向右移动的,匹配可能会倒退,甚至进入死循环,使匹配一直无法结束。如坏字符出现在已经匹配的部分——于是需要配合好后缀来保证向前滑动。
若利用“坏字符规则,目标串中查找指针的移动距离dist=0,那么此时失配处必定不是在模式串的最后一个字符处,即此时必定存在“好后缀”
好后缀的三种情况
- 好的后缀可以在模式串的后缀之前位置的字符串中找到,且该字符串的前一个字符≠P[k] ——中间重合
- 好后缀的的后缀子串是模式串的前缀——首尾重合
- 模式串中找不到子串和好后缀子串前缀, 将模式串整体挪到该字符之后
关键问题: 如何计算目标串中查找指针的移动距离MatchJump?
T中查找指针的移动距离dist≠模式串P的移动距离shift
T中查找指针的移动距离dist=模式串P的移动距离shift+好后缀的长度 —— 打破循环
Step1: MatchJump数组初始化:——默认不存在重复模式
- MatchJump[k]=2*Len(p)-k-1 = Len(p)-k-1+Len(p);(k∈[0,Len(P)-2])—— 已匹配部分长度 + 模式串长度
- MatchJump[Len(P)-1] = 1,最后一个特殊处理,即第一个失配因此只移动一位(意味着:此时没有好后缀)
Step2:若存在好后缀,则依据“好后缀规则 ” , 重新调整
MatchJump[k](k∈[0,Len(P)-2]) (假定:好后缀P’为P[k+1]…P[Len(p)-1] )
- 规则1:若好后缀P’在P中存在重复模式(注意是完全重复!),且重复模式的前一个字符不等,即P[t +1]…P[Len(p)-1-k+t ]== P[k+1]…P[Len(P)-1]且P[t]≠P[k],则MatchJump(k)=[Len(P)-1-k]+min(k-t)——要求计算重复模式的最小间隔,是因为可能有多个重复模式 —— 已匹配部分长度 + 不等字符间距
- 规则2:若不满足规则1,且P的前缀 为好后缀P’中的某个子后缀P”的重复模式(部分重复),即(P”=P[t] … P[Len(p)-1]==P[0] … P[Len(p)-1-t](t>k+1),则MatchJump(k)=[Len(P)-1-k]+min(t)
- 都不满足:MatchJump[k]=2*Len(p)-k-1 = Len(p)-k-1+Len(p);
1 |
|