KMP算法
KMP算法的思想就是:在匹配过程称,若发生不匹配的情况,如果next[j]>=0,则目标串的指针i不变,将模式串的指针j移动到next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比较。
next[j]是什么呢?
即求算模式串每个位置处的最长后缀与前缀相同的长度
就是求一个字符串中索引为j的字符前面的字符串中找出一个以0开始,一个以j-1结束的相等的子字符串。
就是在一个小字符串中找出长度为k的一个最大子字符串,要求字符串p中p[0,k-1]==p[j-k,j-1],这里的最大子字符串不能等于自身,我们就称一下为靠边大子字符串。
例:字符串 ababacd
子字符串 next[j]/k(第一个定义) j
ababacd /ababacd “” -1 0
ababacd a/babacd “” 0 1
ababacd ab/abacd “” 0 2
ababacd aba/bacd “a" 1 3
ababacd abab/acd "ab" 2 4
ababacd ababa/cd "aba“ 3 5
ababacd ababac/d ”“ 0 6
就是像这样,当j=5时,前面的字符串为ababa,在ababa中前3个aba与后3个aba就是上面说的靠边大子字符串,这里k=3,j=5.
这里的前面三个都没有子字符串,k应该都为0,但在下面的计算中为了方便把第一个设为-1,这也表明了第一个k特别。
怎么算出一个字符串中的相对应的next[j]数组呢?
static int[] GetNextVal2(string smallstr) { int[] next = new int[smallstr.Length]; //遍历整个字符串 for (int i = 0; i < smallstr.Length; i++) { if (i == 0) next[i] = -1; if (i == 1) next[i] = 0; //遍历字符前的字符串p[0,j-1] for (int k = i - 1; k >= 0; k--) //k==0时,next[i]=0; { string left = smallstr.Substring(0, k); //前子字符串 string right = smallstr.Substring(i - k, k); //后子字符串 if (left == right) { next[i] = k; //把最大的子字符串长度存储在next[i]中 break; //跳出里面的这个循环 } } } return next; }
上面的方法循环太多了,还有一种是按照递推的思想
/// <summary> /// p0,p1....pk-1 (前缀串) /// pj-k,pj-k+1....pj-1 (后缀串) /// </summary> /// <param name="match"></param> /// <returns></returns> static int[] GetNextVal(string smallstr) { //前缀串起始位置("-1"是方便计算) int k = -1; //后缀串起始位置("-1"是方便计算) int j = 0; int[] next = new int[smallstr.Length]; //根据公式: j=0时,next[j]=-1 next[j] = -1; while (j < smallstr.Length - 1) { if (k == -1 || smallstr[k] == smallstr[j]) { //pk=pj的情况: next[j+1]=k+1 => next[j+1]=next[j]+1 next[++j] = ++k; } else { //pk != pj 的情况:我们递推 k=next[k]; //要么找到,要么k=-1中止 k = next[k]; } } return next; }
大体意思为:就上面的字符串ababacd,查询出来的next[j]的数组为-1,0,0,1,2,3,0
p[5]时,它的可能最大的靠边大子字符串就是p[4]的靠边大子字符串加上p[4],
所以在p[4]中查找k时,k=2,j=4,查找后验证p[4],即p[j]是否与k值为next[4]元素p[k]相等,p[2]==p[4],相等的话就是找到了p[5]的靠边大子字符串,k++;
如果不相等呢?有什么捷径找到p[5]的靠边大子字符串?
在上面的这个例子中p[k]==p[j],所以换一下,求字符串p”ababaad"的p[6]的靠边大子字符串
p[6]的靠边大子字符串,可能的靠边大子字符串就是p[5]的靠边大子字符串+p[5],p[5]中k=3,j=5,在这里发现p[5]!=p[3],就是上面讲的不等的情况,该怎么找它的
从p[5]的靠边大子字符串中查找靠边大子字符串a,再与p[6]进行匹配,不行的话从找到的"a“中再找靠边大子字符串,当然这里是没有了,也匹配了,next[6]=1
全部代码
public class Program { static void Main(string[] args) { string zstr = "ababcabababdc"; string mstr = "ababaab"; var index = KMP(zstr, mstr); if (index == -1) Console.WriteLine("没有匹配的字符串!"); else Console.WriteLine("哈哈,找到字符啦,位置为:" + index); Console.Read(); } static int KMP(string bigstr, string smallstr) { int i = 0; int j = 0; //计算“前缀串”和“后缀串“的next int[] next = GetNextVal2(smallstr); for (int x = 0; x < next.Length; x++) { Console.WriteLine(next[x]); } while (i < bigstr.Length && j < smallstr.Length) { if (j == -1 || bigstr[i] == smallstr[j]) { i++; j++; } else { j = next[j]; } } if (j == smallstr.Length) return i - smallstr.Length; return -1; } /// <summary> /// p0,p1....pk-1 (前缀串) /// pj-k,pj-k+1....pj-1 (后缀串) /// </summary> /// <param name="match"></param> /// <returns></returns> static int[] GetNextVal(string smallstr) { //前缀串起始位置("-1"是方便计算) int k = -1; //后缀串起始位置("-1"是方便计算) int j = 0; int[] next = new int[smallstr.Length]; //根据公式: j=0时,next[j]=-1 next[j] = -1; while (j < smallstr.Length - 1) { if (k == -1 || smallstr[k] == smallstr[j]) { //pk=pj的情况: next[j+1]=k+1 => next[j+1]=next[j]+1 next[++j] = ++k; } else { //pk != pj 的情况:我们递推 k=next[k]; //要么找到,要么k=-1中止 k = next[k]; } } return next; } static int[] GetNextVal2(string smallstr) { int[] next = new int[smallstr.Length]; //遍历整个字符串 for (int i = 0; i < smallstr.Length; i++) { //前字符串为空时,k=-1; if (i == 0) next[i] = -1; if (i == 1) //前字符串只有一个字母时,k=0; next[i] = 0; //遍历字符前的字符串p[0,j-1] for (int k = i - 1; k >= 0; k--) //k==0时,next[i]=0; { string left = smallstr.Substring(0, k); //前子字符串 string right = smallstr.Substring(i - k, k); //后子字符串 if (left == right) { next[i] = k; //把最大的子字符串长度存储在next[i]中 break; //跳出里面的这个循环 } } } return next; } }
http://www.matrix67.com/blog/archives/115/
http://www.cnblogs.com/dolphin0520/archive/2011/08/24/2151846.html
http://www.cnblogs.com/huangxincheng/archive/2012/12/01/2796993.html