KMP算法
KMP算法
一、理论
参考于:http://www.cnblogs.com/hxsyl/p/3300344.html
先介绍前缀数组是如何产生的。首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
来看一个例子:chi表示模式串的前i个字符组成的前缀, next[i] = j表示chi中的开始j个字符和末尾j个字符是一样的(注意下标是字符数目),而且对于前缀chi来说,这样的j是最大值。next[i] = j的另外一个定义是:有一个含有j个字符的串,它既是chi的真前缀,又是chi的真后缀。
规定:next[1] = next[0] = 0,这个规定不像0!=1那样,而是确实是这样子,不懂得看上面的前后缀概念。注意:next数组里并不是首尾回文串,而是前缀等于后缀,理解这个对于递推求next数组很重要哟。next[i]就是前缀数组,下面通过1个例子来看如何构造前缀数组。
例:cacca有5个前缀,求出其对应的next数组。前缀2为ca,显然首尾没有相同的字符,next[2] = 0,前缀3为cac,显然首尾有共同的字符c,故next[3] = 1,前缀4为cacc,首尾有共同的字符c,故next[4] = 1,前缀5为cacca,首尾有共同的字符ca,故next[5] = 2。如果仔细观察,可以发现构造next[i]的时候,可以利用next[i-1]的结果。比如abcdabc,模式已求得next[7] = 3,为求next[8],可以直接比较第4个字符和第8个字符,如果它们相等,则next[8] = next[7]+1 = 4,这是因为next[7] = 3保证了前缀ch7的末尾4个字符的前3个字符是一样的。但如果这两个字符不想等呢?那就继续迭代,利用(k=3)k = next[k]的值来求,直到k=0(next[8] = 0)或者字符相等(next[8] = k+1)。
二、例子与实现
已知模式字符串P[1..m]与文本字符串T[1..n],且已知模式字符P[1..q]与文本字符T[s+1..s+q]匹配,那么满足:P[1..k]=T[s1+1..s1+k],其中s1+k=s+q,k是满足条件的最大值,如下图所示。
从图中可以看出,如果P[q+1]与T[s+q+1]不匹配,无需把P[1..k]与T[s1+1..s1+k]的字符先比较,由公式P[1..k]=T[s1+1..s1+k]可知它们肯定是匹配的,同样如果拿P[k+1]与T[s+q+1]比较,如果不匹配,则用k中的前k1(k1满足P[1..k1]=T[s2+1..s2+k1])个当做k,来比较P[k+1]与T[s+q+1],一直下去,直到P[k+1]= T[s+q+1]。
可以用模式与自身进行比较,在指定匹配长度为q的条件下,预先计算出k的值。
具体实现:
q为匹配字符的长度,k为满足等式中P[1..k]=T[s1+1..s1+k]的前缀最长长度值。prefix_func[]存放在匹配了长度为q的条件下k的值。通过prefix_func[],就可以依据相同的算法获得P对T的模式匹配,如下面的KMP算法。
1 int next[100]; 2 3 void GetNext(char *t) 4 { 5 int i = 0, j = -1; //注意j=-1 6 7 if (t == NULL) 8 return; 9 next[0] = -1; 10 while (t[i]) 11 { 12 if (j == -1 || t[i] == t[j]) //为了获得next[i+1],默认next[1]=0 13 { 14 i++; 15 j++; 16 next[i] = j; 17 } 18 else 19 j = next[j]; 20 } 21 } 22 int Kmp(char *s, char *t) 23 { 24 int i = 0, j = 0; //注意i和j的初值 25 26 if (s == NULL || t == NULL) 27 return -1; 28 29 while (s[i] && (j == -1 || t[j])) 30 { 31 if (j == -1 || s[i] == t[j]) //j==-1说明s[i]开头的字串不可能和t匹配,所以s直接到下一个字符去,理解这个很重要 32 { 33 i++; 34 j++; 35 } 36 else 37 j = next[j]; 38 } 39 if (!t[j]) 40 return (i - j); 41 return -1; 42 }
next[i](假设next[i]=k)记录的是P[0~k]==P[i-1-k~i-1],这点和前面的理论稍有不同。