转自翁振宇
字符串匹配---KMP算法
算法实现一
字符串匹配的关键在于减少匹配次数。而KMP算法的核心思想就是匹配数组,根据匹配数组的值来调度匹配字符串的索引减少多余的调度。
匹配数组就是字符串具有的相同前缀、后缀的最大值。比如对于字符串“ababacbbb”有:
t | a | b | a | b | a | c | b | b | b | \0 |
next | -1 | 0 | 0 | 1 | 2 | 3 | 0 | 0 | 0 | - |
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | - |
1、 令next[0]=-1,从第二个字符(j=1)开始计算匹配数组next的值。
2、 j=1,字符b前面只有一个字符,该字符没有前缀、后缀因此next[1]=0。
3、 j=2,字符a前面的字符串“ab”前缀为a、后缀为b,并不相同。因此next[2]=0。
4、 j=3,字符b前面的字符串“aba”前缀有a、ab后缀有ba、a,相同的只有a长为1,因此next[3]=1。
5、 j=4,字符a前面的字符串“abab”前缀有a、ab、aba后缀有bab、ab、b,相同的只有ab长为2,因此next[4]=2。
6、 j=5,字符c前面的字符串“ababa”前缀有a、ab、aba、abab后缀有baba、aba、ba、a,相同的有a、aba,最长为3,因此next[5]=3。
7、 j=6,字符b前面的字符串“ababac”前缀有a、ab、aba、abab、ababa后缀有babac、abac、bac、ac、c没有相同的前后缀,因此next[6]=0。
8、 j=7,字符b前面的字符串“ababacb”前缀有a、ab、aba、abab、ababa、ababac后缀有babacb、abacb、bacb、acb、cb、b没有相同的前后缀,因此next[7]=0。
9、 j=8,字符b前面的字符串“ababacbb”前缀有a、ab、aba、abab、ababa、ababac、ababacb后缀有babacbb、abacbb、bacbb、acbb、cbb、bb、b没有相同的前后缀,因此next[8]=0。
当匹配字符串t与被匹配字符串s比较时,如果出现某个字符不相等,则根据next数组值调整匹配字符串t的下标(j=next[j])。举例来说t=“ababac”,s=“ababdababababac”:
1、 当s[i]与t[j]相同时直接递增下标,当s串与t串遇到不相等的字符时,比如i=4时,则根据next[]匹配数组值来调整t串的下标j。如果s[i]!=t[j]则令j=next[j]。如果改变j后s[i] 与t[j]仍然不相同,则继续修改j直到j=-1为止。当j=-1时,令i、j均加1,t串与s串重新进行匹配。如下图所示。
2、s[4]!=t[4],令j=next[4]=2,跳转到t[2]继续比较。
3、s[4]!=t[2],令j=next[2]=0,跳转到t[0]继续比较。
4、s[4]!=t[0],令j=next[0]=-1,则直接使i++,j++比较s[5]与t[0]。
5、从s[5]、t[0]开始连续5个字符相等,直到s[10]!=t[5],令j=next[5]=3,跳转到t[3]比较。
6、从s[10]、t[3]开始连续两个字符相同,直到s[12]!=t[5],令j=next[5]=3,跳转到t[3]比较。
7、从s[12]、t[3]开始,直到t串结束余下字符都相同,匹配成功。
代码实现
void getnext(char *t, int *next) //获取字符串t的匹配数组值 { int length=strlen(t); next[0]=-1; //匹配数组第一位取-1,作为一个标记点。 int j=-1; //j=-1时可以寻找到标记点 int i=0; while(j<length) { if(j==-1 ||t[j]==t[i]) //j==-1或者前后t[j]==t[i]为真 { j++; i++; next[i]=j; } else j=next[j]; //j根据next[j]的值往回跳转,直到出现字符相等或j为-1 } } int kmp(char *s, char *t) //KMP算法实现函数 { int length_t=strlen(t); int *next=(int *)malloc(length*sizeof(int)); getnext(t,next); //调用去匹配数组值函数 int length_s=strlen(s); int i=0; int j=-1; while(i<length_s && j<length_t) //t串与s串的匹配查找过程 { if(j==-1 || s[i]==t[j]) { j++; i++; } else j=next[j]; //字符不相等是j值回溯 } return ((j<length_t)?0:i-length_t); //返回t串在s串中第一次出现的位置 }
算法实现二
在算法实现一中KMP处理字符串“aaaab”与“aaaacaaaab”的匹配时会会发现,j做了多次无用的跳转。因为“aaaab”的next[]为“-1 0 1 2 3”,j就会出现从3依次跳转到-1的情况,但是当j=3时发现‘a’与‘c’不相同那么就不应该继续比较后面的3个字符(都为‘a’)。因此这里需要对算法一中取匹配数组值的方法做一些调整。当出现“aaaab”这类情况时可以直接跳转到首个字符‘a’处。
t | a | a | a | a | b | c | b | b | b | \0 |
next | -1 | 0 | 1 | 2 | 3 | 0 | 0 | 0 | 0 | - |
next` | -1 | -1 | -1 | -1 | 3 | 0 | 0 | 0 | 0 | - |
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | - |
1、 根据next[]的值做一些调整。当t[i]==t[j]时,使t[i]的next[i]=next[j](i>j)。比如因为前面四个字符均为a,所以其next[i]均与next[0]相等。
2、 如果t[i]!=t[j],则直接将原来的next值保留(实际实现还是令next[i]=j(i>j))
代码实现
void getnext(char *t, int *next) { int length=strlen(t); next[0]=-1; int i=0; int j=-1; while(i<length) { if(j==-1 || t[j]==t[i]) { j++; i++; if(t[j]==t[i]) //当出现相同字符,使后一个字符的next值等于前一个字符的next值 next[i]=next[j]; else next[i]=j; } else j=next[j]; } }