基于C#实现KMP算法

一、BF 算法

如果让你写字符串的模式匹配,你可能会很快的写出朴素的 bf 算法,至少问题是解决了,我想大家很清楚的知道它的时间复杂度为 O(MN),原因很简单,主串和模式串失配的时候,我们总是将模式串的第一位与主串的下一个字符进行比较,所以复杂度高在主串每次失配的时候都要回溯。

二、KMP 算法

刚才我们也说了,主串每次都要回溯,从而提高了时间复杂度,那么能不能在"主串"和"模式串"失配的情况下,主串不回溯呢?而是让"模式串"向右滑动一定的距离,对上号后继续进行下一轮的匹配,从而做到时间复杂度为 O(M+N)呢?所以 kmp 算法就是用来处理这件事情的,下面我们看下简单的例子。

通过这张图,我们来讨论下它的一般推理,假设主串为 S,模式串为 P,在 Si != Pj 的时候,我们可以看到满足如下关系式 Si-jSi-j+1...Sn-1=P0P1...Pj-1。

那么模式 P 应该向右滑动多少距离?也就是主串中的第 i 个字符应与模式串中的哪一个字符进行比较?

假设应该与模式串中的第 k 的位置相比较,假如模式串中存在最大的前缀真子串和后缀真子串,那么有 P0P1...Pk-1=Pj-kPj-k+1...Pj-1.

这句话的意思也就是说,在模式 P 中,前 k 个字符与 j 个字符之前的 k 个字符相同,比如说:"abad"的最大前缀真子串为"aba",最大后缀真子串为"bad",当然这里是不相等,这里的 0<k<j,我们希望 k 接近于 j,那么我们滑动的距离将会最小,好吧,现在我们用 next[j]来记录失配时模式串应该用哪一个字符于 Si 进行比较。

设 next[j]=k。根据公式我们有

next[j] =  -1     当 j=0 时
next[j] =  max{k| 0<k<j 且 P0P1..Pk-1=Pj-kPj-k+1...Pj-1}
next[j] =  0     其他情况

好,接下来的问题就是如何求出 next[j],这个也就是 kmp 思想的核心,对于 next[j]的求法,我们采用递推法,现在我们知道了 next[j]=k,我们来求 next[j+1]=?的问题,其实也就是两种情况:

①:Pk=Pj 时 则 P0P1...Pk=Pj-kPj-k+1...Pj, 则我们知:

next[j+1]=k+1。

又因为 next[j]=k,则

next[j+1]=next[j]+1。

②:Pk!=Pj 时 则 P0P1...Pk!=Pj-kPj-k+1...Pj,其实这里我们又将模式串的匹配问题转化为了上面我们提到的"主串"和"模式串"中寻找 next 的问题,你可以理解成在模式串的前缀串和后缀串中寻找 next[j]的问题。现在我们的思路就是一定要找到这个 k2,使得 Pk2=Pj,然后将 k2 代入 ① 就可以了。

设 k2=next[k]。 则有 P0P1...Pk2-1=Pj-k2Pj-k2+1...Pj-1。

若 Pj=Pk2, 则 next[j+1]=k2+1=next[k]+1。

若 Pj!=Pk2, 则可以继续像上面递归的使用 next,直到不存在 k2 为止。

好,下面我们上代码。

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SupportCenter.Test
{
  public class Program
  {
     static void Main(string[] args)
     {
         string zstr = "ababcabababdc";

         string mstr = "babdc";

         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 = GetNextVal(smallstr);

         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;
     }
 }
}
相关推荐
&梧桐树夏3 分钟前
【算法系列-链表】删除链表的倒数第N个结点
数据结构·算法·链表
QuantumStack7 分钟前
【C++ 真题】B2037 奇偶数判断
数据结构·c++·算法
今天好像不上班16 分钟前
软件验证与确认实验二-单元测试
测试工具·算法
wclass-zhengge1 小时前
数据结构篇(绪论)
java·数据结构·算法
何事驚慌1 小时前
2024/10/5 数据结构打卡
java·数据结构·算法
结衣结衣.1 小时前
C++ 类和对象的初步介绍
java·开发语言·数据结构·c++·笔记·学习·算法
大二转专业3 小时前
408算法题leetcode--第24天
考研·算法·leetcode
凭栏落花侧3 小时前
决策树:简单易懂的预测模型
人工智能·算法·决策树·机器学习·信息可视化·数据挖掘·数据分析
hong_zc4 小时前
算法【Java】—— 二叉树的深搜
java·算法
吱吱鼠叔5 小时前
MATLAB计算与建模常见函数:5.曲线拟合
算法·机器学习·matlab