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

原文地址:https://www.cnblogs.com/hongdada/p/2892718.html