大多的浏览器都有在网页中需找关键字的功能。
这个功能的实现涉及到字符串的匹配算法。
在大学中数据结构考试的时候大家对KMP算法一定是深恶痛绝。不过事物往往是有其两面性的。
如今KMP就成为了我的救世主。
首先对比下普通字符串匹配BF算法与KMP算法的效率。
设:原始串是S,模式串是T。
时间复杂度: BF算法是O(strlen(S) * strlen(T))。
空间复杂度: BF算法是O(1)。
现在明显的看出KMP在时间上的优越性了吧。对于KMP比BF稍差的空间复杂度,相信大多的硬件设备是可以提供足够的空间的。
对比下普通字符串匹配BF算法与KMP算法的匹配方式
BF算法中,如果当前字符匹配成功,即s[i+j] == T[j],令j++,继续匹配下一个字符;如果失配,即S[i + j] != T[j],需要让i++,并且j= 0,即每次匹配失败的情况下,模式串T相对于原始串S向右移动了一位。
而KMP算法中,如果当前字符匹配成功,即S[i]==T[j],令i++,j++,继续匹配下一个字符;如果匹配失败,即S[i]
要是按照普通字符串匹配BF算法,字符串的匹配是按照S串逐个进行匹配的,每次的回溯都是strlen(T)-1,这注定了时间效率的低下。
能不能不进行回溯,直接跳到S串不匹配的字符处?显然是不行的。
如模式串中包含重复出现的字符,比如ababc。当和abababc进行匹配时:
abababc
ababc
匹配到c时如果不进行回溯的话:
abababc
将找不到匹配的字符
但是也有特殊的情形如abcde和ababcde进行匹配,在模式串中没有重复出现的字符。那么可以不进行回溯。
ababcde
abcde
ababcde
显然可以看出回溯的原因是因为模式串中有重复出现的字符,如果不回溯可能会跳过匹配的字符串。用比较专业的说法是模式串中所有字符串的“前缀”和“后缀”的相等部分的长度不为0。模式串长度为n,则存在n个字符串的“前缀”和“后缀”。
什么叫做“前缀”和“后缀”,非专业人士的认识如下:
例如:
设模式串T0,T1,T2,T3,T4为ababc
则ababc中aba字符串的前缀和后缀相同的部分为T0和T2,长度为1.
ababc中abab字符串的前缀和后缀相同的部分为T0T1和T2T3,长度为2.
如果模式串T0,T1,T2,T3,T4为abcde
则abcde中所有字符串的前缀和后缀相同的部分长度为0.如:a,ab,abc,abcd,abcde
注意:只要模式串中的某个字符串的前缀和后缀有相同的部分,则这个模式字符串匹配时将需要进行回溯。
KMP
...ababd...
这样i不用回溯,j跳到前2个位置,继续匹配的过程,这就是KMP算法所在。这个当T[j]失配后,j
next数组的含义
重点来了。下面解释一下next数组的含义,这个也是KMP算法中比较不好理解的一点。
next数组求法
可以证明对于任意的模式串T=T0T1…Tm-1,确实存在一个由模式串本身唯一确定的与目标串无关的数组next,计算方法为:
(3) 当k≠-1且Ti-1 ≠ Tk,则令 k = next [k];
(非优化的)
简单举例说明下计算方法:
还是上面的例子设模式串T0,T1,T2,T3,T4为ababc
当i=0,next[0]=-1
当i=1,next[1]=0;
当i=2,(1)next[1]=0, => k=0
当 i=3,(1)next[2]=0, =>k=0
当 i=4,(1)next[3]=1,=>k=1
void get_next(string s,int next[]) { int length=s.length(); int i=0,j=-1; next[0]=-1; while(i<length) { if(j==-1||s[i]==s[j]) { ++i; ++j; next[i]=j; } else j=next[j]; } }
//代码5-1 //int kmp_seach(char const*, int, char const*, int, int const*, int pos) KMP模式匹配函数 //输入:src, slen主串 //输入:patn, plen模式串 //输入:nextval KMP算法中的next函数值数组 int kmp_search(char const* src, int slen, char const* patn, int plen, int const* nextval, int pos) { int i = pos; int j = 0; while ( i < slen && j < plen ) { if( j == -1 || src[i] == patn[j] ) { ++i; ++j; } else { j = nextval[j]; //当匹配失败的时候直接用p[j_next]与s[i]比较, //下面阐述怎么求这个值,即匹配失效后下一次匹配的位置 } } if( j >= plen ) return i-plen; else return -1; }
参考:
http://blog.csdn.net/liuben/article/details/4409505
http://blog.csdn.net/v_july_v/article/details/7041827