快速理解KMP算法——串的模式匹配改进算法
快速理解KMP算法——串的模式匹配改进算法
关于数据结构中对串的操作,除了一些基本操作如增删、复制和比较之外,还有一种就是串的模式匹配。翻译下 就是定位已知子串在主串中的位置。
下面我们用一个例子来演示。
一个简单例子
主串:a c a b a b c a
子串:a b c
我们要做的是在主串中查找子串的位置。
- 下面用A表示主串,B表示子串
- 指针 i :主串中当前比较的元素位置
- 指针 j :子串中当前比较的元素位置
原始暴力算法
我们先用一种简单的方法实现这个功能。
我们从开头开始比较
-
第一次匹配
A:a c a b a b c a
B:a b c
开始时的位置:i1
停止时的位置:i2 j2 -
第二次匹配
A:a c a b a b c a
B: a b c
开始时的位置:i2
停止时的位置:i2 j1 -
第三次匹配
A:a c a b a b c a
B: a b c
开始时的位置:i3
停止时的位置:i5 j3 -
第四次匹配
A:a c a b a b c a
B: a b c
开始时的位置:i4
停止时的位置:i4 j1 -
第五次匹配
A:a c a b a b c a
B: a b c
开始时的位置:i5
停止时的位置:i8 j4(结束)
此种方法的时间复杂度在最坏的情况下达到了(主串长度*子串长度)!!!
这是因为 i 还需要回退
KMP改进算法
KMP算法的改进之处是它对子串做了一定的处理,使得每次匹配失败后,下一次匹配开始的位置尽可能的向后移。例如上面的例子A,如果用KMP算法,在第三次匹配后就可以跳过第四次匹配,直接进行第五次匹配。(i 不需要回退到4)
KMP的基本流程
下面我们具体来实现这种算法:
再次解释一下
- 指针 i :主串中当前比较的元素位置
- 指针 j :子串中当前比较的元素位置
通过比较我们发现KMP算法和原始算法的区别就是在匹配失败的时候i不用回退(i -= j + 2),通过next[]这个数组确定下一个j值就可以。
所以KMP算法的核心还是求出子串的next[]数组
下面给出next[j]的定义
如何理解next这个函数呢?
下图表示模式匹配时的子串B,
如图,当我们比较到D的开头时,发现与主串不匹配。因为我们已经比较过一次C和主串中对应位置是相等的,而A又与C相等,所以我们直接从B的开头匹配就行了(B的开头也就是A或C的长度+1)。
求next数组
求的时候我们可以用递推的方法:
设next[j] = m 如何求next[j+1]?
首先next[j] = m 表示 B[1] ~ B[m-1]与 B[j-m+1] ~ B[j - 1]是相等的
而当j 滑动到j+1处时,有两种情况:
- B[m] == B[j]
此时直接累加m的值即可 - B[m] != B[j]
此时就要向前寻找下一个B[m],使B[m] == B[j]。令m = next[m],再次判断。(这个地方和上面KMP的算法有点像)
举个例子
a b c a b d a a a
0 1 1 1 2 3 1 2
next[8] = 2 表示B[1] == B[8]
比较B[2]和B[9],发现不相等
m = next[2] = 1
比较B[1]和B[9],发现相等。
累加m的值为2,即next[9] = 2
next求法代码表示
next[1] = 0;
m = 0; //表示子串中的相同前后缀最长长度+1(初始为0)
j = 1; //表示扫描时的位置
while(j < B_len)
{
if(m == 0 || B[m] == B[j])
{
m++;
j++;
next[j] = m;
}
else
m = next[m];
}
总结
KMP算法的理论大概就这样,上面的流程图为了保证简洁省略了一些特殊情况,主要还是有助于理解,实际程序自己写一写就好了。