字符串匹配问题
查找某个长度为m的字符串P在文本字符串中出现的位置,文本串T的长度为n。
- 求P在T中第一次出现的位置
- 求P在T中所有出现的位置
一般解法
首先使用比较直观的平凡算法来求解该问题。基本思路是扫描T,先找到P第一个字符的出现位置,再继续匹配其后续的若干个字符,若出现不同则匹配失败返回,继续向后查找P的第一个字符。
时间复杂度为O(mn)阶。
KMP算法
KMP(Knuth-Morris-Pratt)算法主要是在匹配失败时做了改进。它利用模式串P中可能出现重复的特征,使每次P与T匹配失败后,不会再从头开始扫描P。
为P定义next数组
P中下标为i的字符的next[i]定义为:字符i之前的子串中相同前后缀的最大长度。
例子
定义 T: abababdaa P: abababcd
当扫描到下标为5的字符‘c’时匹配失败:
T: ababab|d|aa
|
P: ababab|c|d
字符c之前的子串为ababab,有最长的相同前缀和后缀为abab,故next[5]为4。
KMP
接上文,匹配失败后将P右移next[5]位,继续下一次匹配。
例子
T: ababab|d|aa
|
P: ab|a|babcd
可以看到,next[i]是在该字符处匹配失败时P应该右移的字符数,也就是扫描指针应该左移的字符个数。
性能讨论
字符集大小的影响
T和P可选择的字符集越大,则越不容易匹配成功,每次匹配失败的位置会更加靠前,性能较接近于平凡算法。
模式串P的特征
如果P的长度m较小,则性能改进不会很明显;
若P中重复很少,性能也不会太好,因为每次搜索时P实际右移的距离不大,与平凡解法接近。
较好情况
若字符集小且m较大时,KMP算法跳跃扫描的优势就能得到体现,可以得到较明显的性能改进。
参考
《计算机算法引论》