1、KMP算法由来
字符串匹配问题描述:参见
LeetCode OJ:Implement strStr()
关于这个问题,最简单的想法就是扫描字符串s,一个一个进行比对,具体实现参见问题解答
2、简单匹配算法,BF算法,思想较简单,不做解释
class Solution { public: int BF(char *S ,char *T) { int slen = strlen(S); int tlen = strlen(T); int i = 0 ,j = 0; while ( i+j < slen && j < tlen){ if ( S[i+j] == T[j] ){ j ++; } else { i ++; j = 0; } } if ( j >= tlen) return i; else return -1; } };
3、KMP算法
(1)由来:
因为BF算法时间复杂度为O(M*N),每一次不匹配后都需要跳回S字符串的下一个字符出进行
举个例子,假设T字符串中的字符两两不同,那么当循环到某一位置不匹配时,压根就不需要回到S的下一个i位置处进行,直接从当前位置处向后循环比对就可以了,因为T字符串两两不同,前面的字符根本不可能作为第一个匹配的字符, 所以,从这来看,可以设计这样一种算法,该算法时间复杂度取决于T字符串中相同字符的个数,因此,KMP算法应运而生。时间复杂度可提高为O(M+N)
(2)模式值next[n]
简单地说,模式值next[n]就是当字符串不匹配时,不是回到字符串的起点,而是回到某个位置,next[n]记录第n位置元素不匹配时应该反回到的位置,然后向后进行匹配
(3)next[n]函数
至此,主要问题归结于求模式值next[n]
定义:
(1)next[0]= -1 意义:任何串的第一个字符的模式值规定为-1。
(2)next[j]= -1 意义:模式串T中下标为j的字符,如果与首字符相同,且j的前面的1—k个字符与开头的1—k个字符不等(或者相等但T[k]==T[j])(1≤k<j)。
如:T=”abCabCad” 则 next[6]=-1,因T[3]=T[6]
(3)next[j]=k 意义:模式串T中下标为j的字符,如果j的前面k个字符与开头的k个字符相等,且T[j] != T[k] (1≤k<j)。
即T[0]T[1]T[2]。。。T[k-1]==T[j-k]T[j-k+1]T[j-k+2]…T[j-1]且T[j] != T[k].(1≤k<j);
(4) next[j]=0 意义:除(1)(2)(3)的其他情况。
举例:
01)求T=“abcac”的模式函数的值。
next[0]= -1 根据(1)
next[1]=0 根据 (4) 因(3)有1<=k<j;不能说,j=1,T[j-1]==T[0]
next[2]=0 根据 (4) 因(3)有1<=k<j;(T[0]=a)!=(T[1]=b)
next[3]= -1 根据 (2)
next[4]=1 根据 (3) T[0]=T[3] 且 T[1]=T[4]
即
下标 |
0 |
1 |
2 |
3 |
4 |
T |
a |
b |
c |
a |
c |
next |
-1 |
0 |
0 |
-1 |
1 |
若T=“abcab”将是这样:
下标 |
0 |
1 |
2 |
3 |
4 |
T |
a |
b |
c |
a |
b |
next |
-1 |
0 |
0 |
-1 |
0 |
为什么T[0]==T[3],还会有next[4]=0呢, 因为T[1]==T[4], 根据 (3)” 且T[j] != T[k]”被划入(4)。
02)来个复杂点的,求T=”ababcaabc” 的模式函数的值。
next[0]= -1 根据(1)
next[1]=0 根据(4)
next[2]=-1 根据 (2)
next[3]=0 根据 (3) 虽T[0]=T[2] 但T[1]=T[3] 被划入(4)
next[4]=2 根据 (3) T[0]T[1]=T[2]T[3] 且T[2] !=T[4]
next[5]=-1 根据 (2)
next[6]=1 根据 (3) T[0]=T[5] 且T[1]!=T[6]
next[7]=0 根据 (3) 虽T[0]=T[6] 但T[1]=T[7] 被划入(4)
next[8]=2 根据 (3) T[0]T[1]=T[6]T[7] 且T[2] !=T[8]
即
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
T |
a |
b |
a |
b |
c |
a |
a |
b |
c |
next |
-1 |
0 |
-1 |
0 |
2 |
-1 |
1 |
0 |
2 |
只要理解了next[3]=0,而不是=1,next[6]=1,而不是= -1,next[8]=2,而不是= 0,其他的好象都容易理解。
03) 来个特殊的,求 T=”abCabCad” 的模式函数的值。
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
T |
a |
b |
C |
a |
b |
C |
a |
d |
next |
-1 |
0 |
0 |
-1 |
0 |
0 |
-1 |
4 |
next[5]= 0 根据 (3) 虽T[0]T[1]=T[3]T[4],但T[2]==T[5]
next[6]= -1 根据 (2) 虽前面有abC=abC,但T[3]==T[6]
next[7]=4 根据 (3) 前面有abCa=abCa,且 T[4]!=T[7]
若T[4]==T[7],即T=” adCadCad”,那么将是这样:next[7]=0, 而不是= 4,因为T[4]==T[7].
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
T |
a |
d |
C |
a |
d |
C |
a |
d |
next |
-1 |
0 |
0 |
-1 |
0 |
0 |
-1 |
0 |
意义:
next 函数值究竟是什么含义,前面说过一些,这里总结。
设在字符串S中查找模式串T,若S[m]!=T[n],那么,取T[n]的模式函数值next[n],
1. next[n]= -1 表示S[m]和T[0]间接比较过了,不相等,下一次比较 S[m+1]和T[0]
2. next[n]=0 表示比较过程中产生了不相等,下一次比较 S[m] 和T[0]。
3. next[n]= k >0 但k<n, 表示,S[m]的前k个字符与T中的开始k个字符已经间接比较相等了,下一次比较S[m]和T[k]相等吗?
4. 其他值,不可能。
所以next[n]函数为void get_nextval(char const* ptrn, int plen, int* nextval) { int i = 0; nextval[i] = -1; int j = -1; while( i < plen-1 ) { if( j == -1 || ptrn[i] == ptrn[j] ) { ++i; ++j; if( ptrn[i] != ptrn[j] ) nextval[i] = j; else nextval[i] = nextval[j]; } else j = nextval[j]; } }
当next[n]生成后,其他的和BF就一样了,j=0换成j=nextval[j]而已
class Solution { public: void get_nextval(char const* ptrn, int plen, int* nextval) { int i = 0; nextval[i] = -1; int j = -1; while( i < plen-1 ) { if( j == -1 || ptrn[i] == ptrn[j] ) { ++i; ++j; if( ptrn[i] != ptrn[j] ) nextval[i] = j; else nextval[i] = nextval[j]; } else j = nextval[j]; } } int KMP(char *S, char *T) { int slen = strlen(S); int tlen = strlen(T); int i = 0 ,j = 0; int *nextval=new int[tlen]; get_nextval(T,tlen,nextval); while ( i+j < slen && j < tlen){ if ( j == -1 || S[i+j] == T[j] ){ j ++; } else { i ++; j = nextval[j]; } } if ( j >= tlen) return i; else return -1; } };