1.kmp算法
1.1.kmp的简单介绍
算法复杂度 O(n+m)
略
1.2. 实现原理
1.2.1.改进——相比于普通模式匹配的改进
每当一趟匹配过程中出现字符比较不等时, 不需要回溯i指针,而是利用已经得到的“部分匹配”的结果将模式串向右“滑动”尽可能远的一段距离后,继续进行比较。
个人:尽可能向右滑动。 就是为了尽量减少不必要的匹配,提高效率。
1.2.2.实现改进需要解决的问题
假设主串为"s1s2…sn", 模式串为“p1p2…pn”
为了实现改进算法, 需要解决下述问题:当匹配过程中产生“失配”(即si!=pj)时, 模式串“向右滑动”可行的距离多远。换句话说, 当主串第i个字符与模式串第j个字符“失配”时, 主串中第i个字符在不回溯的情况下应与模式串中哪个字符在比较?
假设与第k(k < j)个继续比较, 则模式串中前k-1个字符串必须满足下列关系, 并且不可能存在k‘ >k满足下列关系
’
p
1
p
2
.
.
.
.
p
k
−
1
′
=
′
s
i
−
k
+
1
s
i
−
k
+
2
.
.
.
s
i
−
1
′
’p_{1}p_{2}....p_{k-1} ' = 's_{i-k+1}s_{i-k+2}...s_{i-1}'
’p1p2....pk−1′=′si−k+1si−k+2...si−1′
而已经得到的“部分匹配”结果
’
p
j
−
k
+
1
p
j
−
k
+
2
.
.
.
.
p
j
−
1
′
=
′
s
i
−
k
+
1
s
i
−
k
+
2
.
.
.
s
i
−
1
′
’p_{j-k+1}p_{j-k+2}....p_{j-1} ' = 's_{i-k+1}s_{i-k+2}...s_{i-1}'
’pj−k+1pj−k+2....pj−1′=′si−k+1si−k+2...si−1′
因此,可得
’
p
1
p
2
.
.
.
.
p
k
−
1
′
=
‘
p
j
−
k
+
1
p
j
−
k
+
2
.
.
.
.
p
j
−
1
′
’p_{1}p_{2}....p_{k-1} '=‘p_{j-k+1}p_{j-k+2}....p_{j-1} '
’p1p2....pk−1′=‘pj−k+1pj−k+2....pj−1′
个人:从此可知, 与k继续比较,⇔ p[1j-1]串的,前缀[1k-1] = 后缀 ,并且是最大的相似前后缀
个人:由此, 将模式串每个字符失配后进行比较的位置记录下来则引入next函数。
若令next[j] = k,则next[j]表明当模式中第j个字符与主串中相应字符“失配”时, 在模式中需要重新和主串中该字符进行比较的字符位置。由此可孕畜模式串的next函数定义:
个人:next[j] = 0时, 就是第一个字符都失配,所以通过0来是他向右移动一位,
其他情况就是,最大相似前后缀为0, 即没有最大相似前后缀的情况。
1.2.3.在假设已知next函数时匹配过程如下
在匹配时有两种情况:
- si = pj,则i和j增加1
- 否则, i不变, j退到next[j]的位置。
- 若相等则各自增加1
- 否则, 退到next[next[j]]的位置,再比较,以此类推。
- 这时会产生两种结果:
- 退到某个next[…next[j]…]时, 字符比较相等, 各自加1
- 否则j退到值为0, 此时模式串向右滑动一个位置
- 这时会产生两种结果:
则kmp的代码如下
//该字符串下标从1开始, 下标0存的是字符串的长度,。
int Index_KMP(SString s, SString t, int pos){
int i = pos,j = 1;
while(i <= s[0]&&j<=t[0]){
if(j==0|| s[i]==t[j]){
i++;j++;
}
else j = next[j];
}
if (j>T[0])return i - T[0];
else return 0;
}
1.2.3. 通过计算机语言实现next函数
前面我们可以通过定义手动计算出next函数的值。接下来要通过计算机语言来推出next函数。
上述可见,next函数只与模式串本省有关,与主串无关。我们可以分析其定义出发用递推的方法求得next函数值。
个人:递推, 就是从已知结果依次推出未知的结果
由定义可知
next[1] = 0
个人:此为最初的已知结果
设next[j] = k ,这表明在模式串中存在下列关系:
’
p
1
p
2
.
.
.
.
p
k
−
1
′
=
‘
p
j
−
k
+
1
p
j
−
k
+
2
.
.
.
.
p
j
−
1
′
’p_{1}p_{2}....p_{k-1} '=‘p_{j-k+1}p_{j-k+2}....p_{j-1} '
’p1p2....pk−1′=‘pj−k+1pj−k+2....pj−1′
其中k满足 1 < k < j , 并且不可能存在k’ > k 满足以上等式。此时next[j+1] = ?可能有两种情况:
个人:以下是由已知推未知的过程
-
若pk = pj, 则表明
’ p 1 p 2 . . . . p k ′ = ‘ p j − k + 1 p j − k + 2 . . . . p j ′ ’p_{1}p_{2}....p_{k} '=‘p_{j-k+1}p_{j-k+2}....p_{j} ' ’p1p2....pk′=‘pj−k+1pj−k+2....pj′
并且不可能存在k‘ >k ,满足上述等式,所以next[j+1] = k+1,即
n e x t [ j + 1 ] = n e x t [ j ] + 1 next[j+1] = next[j]+1 next[j+1]=next[j]+1 -
若pk≠pj, 则表明
’ p 1 p 2 . . . . p k ≠ ‘ p j − k + 1 p j − k + 2 . . . . p j ′ ’p_{1}p_{2}....p_{k} ≠‘p_{j-k+1}p_{j-k+2}....p_{j} ' ’p1p2....pk=‘pj−k+1pj−k+2....pj′
此时又可以把求next函数值, 看做成一个模式匹配问题。在该问题中,模式串即是主串又是模式串。所以当pk≠pj时, 下一个匹配的字符为next[k](设next[j] = x)。- 若px = pj, 则说明主串中第j+1个字符串之前存在一个长度为x的最长子串和模式串从首字母起长度为x的子串相等
’ p 1 p 2 . . . . p x ≠ ‘ p j − x + 1 p j − k + 2 . . . . p j ′ ’p_{1}p_{2}....p_{x} ≠‘p_{j-x+1}p_{j-k+2}....p_{j} ' ’p1p2....px=‘pj−x+1pj−k+2....pj′
所以,next[j+1] = next[k]+1
-
若px!= pj, 则继续与next[x]比较, 依次类推。
-
若存在next[…next[x]…]字符 = pj
next[j+1] = …
-
否则next[j+1] = 1
-
由此可得出, 递推球next函数的代码:
void get_next(SString T, int next[]){
//求模式串T的next函数值, 存入数组next。
int j = 1;// 初始为1表示第一个字符
next[1] = 0;
k = 0;//第一个字符的k为0
while(j < T[0]){
if(k == 0 || next[k] == next[j]){
next[++j] = ++k;
}
else k = next[k];
}
}
1.2.4. 通过c实现的kmp算法
大多数语言字符串下标从0开始,所以我们以c为例实现kmp
void get_next(char* T, int next[]){
int j = 0;
next[0] = -1;
k = -1;
int len = strlen(T);
while(j < len-1){
if(k == -1|| next[k] = next[j]){
next[++j] = ++next[k];
}
else k = next[k];
}
}
void kmp(char * s, char * t, int pos){
int i = pos;
int j = 0;
int slen = strlen(s);
int tlen = strlen(t);
while(i < slen && j < tlen){
if(j == -1||next[i] == next[j]){
i++;
j++;
}
else j = next[j];
}
if(j == tlen)return i - j;
else return -1;
}
参考
- 严敏 吴伟民 编 数据结构(C语言版)