为了避免朴素匹配算法需要向左回溯导致效率较低的缺点,引进了无需回溯的KMP算法。
KMP算法
利用模式串P自身的重复模式
关注:
- 匹配失败,是否发生在P的第一个字符处?
- 若当前轮匹配在进行第一个字符比较时就失败,那么
下一轮应该是比较T[i+1]和P[1]
- 若当前轮匹配在进行第一个字符比较时就失败,那么
- P中是否有重复模式?
- 若P中在当前轮成功匹配的子串的后缀与子串的前缀
无重复模式,那么下一轮应该是比较T[i]和P[1] - 若P中在当前轮成功匹配的子串的后缀与子串的前缀
有重复模式,那么下一轮应该是比较T[i]和P[next[j]] - 模式串的next[j] = ? next[j]就是第j个元素前j-1个元素首尾重合部分个数加1
- 规定任何一个串,next[1]=0
- next[i]= [P串中前 i-1 子串首尾最长匹配数 + 1] —— 首尾重合不包括本身
- 其他情况,next[i]= 1
- 若P中在当前轮成功匹配的子串的后缀与子串的前缀
匹配过程:若某轮匹配失败,则利用next数组分别计算下一轮匹配时目标串和模式串的开始位置
若是T[i]≠P[j]导致当前轮的匹配失败,则按照下列规则开始下一轮匹配:
- 若next[j] ≠ 0,则将T[i..]与P[next[j]..]匹配
- 若next[j]==0,则将T[(i+1)..]与P[1..]匹配
nextval数组
什么情况下有改进的空间?
假设T[i] ≠P[j]导致失配 。若P[j]==P[next[j]],此时若向右移动模式串P,将T[i]与P[next[j]]对齐进行比较必然是无意义的,因为此时T[i]必定≠P[next[j]]。
如何改进?用nextval数组代替next数组。
- nextval[1]=0;
- for(j>1;j<=n;j++)
若P[j]==P[next[j]],则nextval[j]=nextval[next[j]];
若P[j]≠P[next[j]],则nextval[j]=next[j];
若某轮匹配失败,则利用nextval数组计算下一轮匹配时的目标串和模式串的开始位置(类似next数组的应用) - 一直比到相等为止
KMP算法近似时间复杂度为O(n+m),其中O(n)表示比较的时间, O(m)表示计算next数组的时间.
若每轮中模式串与目标串之间的不匹配都发生在模式串的第一个字符处,则KMP算法会退化到朴素模式匹配算法。
1 |
|