【问题标题】:What's wrong with my implementation of the KMP algorithm?我的 KMP 算法的实现有什么问题?
【发布时间】:2011-08-22 17:51:19
【问题描述】:
static void Main(string[] args)
{
    string str = "ABC ABCDAB ABCDABCDABDE";//We should add some text here for 
                                           //the performance tests.

    string pattern = "ABCDABD";


    List<int> shifts = new List<int>();

    Stopwatch stopWatch = new Stopwatch();

    stopWatch.Start();
    NaiveStringMatcher(shifts, str, pattern);
    stopWatch.Stop();
    Trace.WriteLine(String.Format("Naive string matcher {0}", stopWatch.Elapsed));

    foreach (int s in shifts)
    {
        Trace.WriteLine(s);
    }

    shifts.Clear();
    stopWatch.Restart();

    int[] pi = new int[pattern.Length];
    Knuth_Morris_Pratt(shifts, str, pattern, pi);
    stopWatch.Stop();
    Trace.WriteLine(String.Format("Knuth_Morris_Pratt {0}", stopWatch.Elapsed));

    foreach (int s in shifts)
    {
        Trace.WriteLine(s);
    }

    Console.ReadKey();
}

static IList<int> NaiveStringMatcher(List<int> shifts, string text, string pattern)
{
    int lengthText = text.Length;
    int lengthPattern = pattern.Length;

    for (int s = 0; s < lengthText - lengthPattern + 1; s++ )
    {
        if (text[s] == pattern[0])
        {
            int i = 0;
            while (i < lengthPattern)
            {
                if (text[s + i] == pattern[i])
                    i++;
                else break;
            }
            if (i == lengthPattern)
            {
                shifts.Add(s);                        
            }
        }
    }

    return shifts;
}

static IList<int> Knuth_Morris_Pratt(List<int> shifts, string text, string pattern, int[] pi)
{

    int patternLength = pattern.Length;
    int textLength = text.Length;            
    //ComputePrefixFunction(pattern, pi);

    int j;

    for (int i = 1; i < pi.Length; i++)
    {
        j = 0;
        while ((i < pi.Length) && (pattern[i] == pattern[j]))
        {
            j++;
            pi[i++] = j;
        }
    }

    int matchedSymNum = 0;

    for (int i = 0; i < textLength; i++)
    {
        while (matchedSymNum > 0 && pattern[matchedSymNum] != text[i])
            matchedSymNum = pi[matchedSymNum - 1];

        if (pattern[matchedSymNum] == text[i])
            matchedSymNum++;

        if (matchedSymNum == patternLength)
        {
            shifts.Add(i - patternLength + 1);
            matchedSymNum = pi[matchedSymNum - 1];
        }

    }

    return shifts;
}

为什么我的 KMP 算法的实现比 Naive String Matching 算法慢?

【问题讨论】:

  • @minitech naive function 是一个乍一看似乎正确的函数,因为它是第一个出现的解决方案。然而,在计算科学中,可以有更好的设计,例如 KMP 算法优于朴素算法。

标签: c# algorithm performance string


【解决方案1】:

KMP 算法有两个阶段:首先它建立一个表,然后根据表的内容进行搜索。

朴素算法有一个阶段:它进行搜索。 在最坏的情况下,它的搜索效率远低于 KMP 搜索阶段。

如果 KMP 比朴素算法慢,那么 可能 因为构建表所花费的时间比首先简单地搜索字符串所花费的时间要长。朴素的字符串匹配通常在短字符串上非常快。我们在字符串搜索的 BCL 实现中不使用像 KMP 这样的花哨算法是有原因的。到您设置表格时,您可能已经使用简单算法对短字符串进行了六次搜索。

只有当你有 巨大的 个字符串并且你正在执行 lots 的搜索以允许你重新使用已经构建的表时,

KMP 才是一个胜利。您需要通过使用该表进行大量搜索来分摊构建该表的巨大成本。

而且,朴素算法仅在奇异且不太可能的情况下表现不佳。大多数人在诸如“Buckingham Palace, London, England”之类的字符串中搜索诸如“London”之类的词,而不是在诸如“BANAN BANBAN BANBANANA BANAN BANAN BANANAN BAANANANANANANANANAN...”之类的字符串中搜索诸如“BANANANANANANA”之类的字符串。朴素搜索算法对第一个问题是最优的,而对后一个问题是高度次优的;但优化前者而不是后者是有意义的。

换一种说法:如果搜索到的字符串长度为w,搜索到的字符串长度为n,那么KMP就是O(n) + O(w)。朴素算法是最坏情况 O(nw),最好情况 O(n + w)。但这并没有说明“恒定因素”! KMP 算法的常数因子远大于朴素算法的常数因子。 n 的值必须非常大,次优部分匹配的数量必须非常大,KMP 算法才能战胜速度极快的朴素算法。

这涉及算法复杂性问题。你的方法也不是很好,这可能解释了你的结果。请记住,第一次运行代码时,抖动必须将 IL 抖动为汇编代码。 在某些情况下,这可能需要比运行该方法更长的时间。你真的应该在一个循环中运行代码几十万次,丢弃第一个结果,并取其余时间的平均值。

如果您真的想知道发生了什么,您应该使用分析器来确定热点是什么。同样,如果您希望得到不受 jit 时间影响的结果,请确保您测量的是 jit 后运行,而不是代码被 jit 的运行。

【讨论】:

    【解决方案2】:

    您的示例太小,并且没有足够的重复 KMP 避免回溯的模式。

    在某些情况下,KMP 可能比正常搜索慢。

    【讨论】:

      【解决方案3】:

      一个简单的 KMPSubstringSearch 实现。

      https://github.com/bharathkumarms/AlgorithmsMadeEasy/blob/master/AlgorithmsMadeEasy/KMPSubstringSearch.cs

      using System;
      using System.Collections.Generic;
      using System.Linq;
      
      namespace AlgorithmsMadeEasy
      {
          class KMPSubstringSearch
          {
              public void KMPSubstringSearchMethod()
              {
                  string text = System.Console.ReadLine();
                  char[] sText = text.ToCharArray();
      
                  string pattern = System.Console.ReadLine();
                  char[] sPattern = pattern.ToCharArray();
      
                  int forwardPointer = 1;
                  int backwardPointer = 0;
      
                  int[] tempStorage = new int[sPattern.Length];
                  tempStorage[0] = 0;
      
                  while (forwardPointer < sPattern.Length)
                  {
                      if (sPattern[forwardPointer].Equals(sPattern[backwardPointer]))
                      {
                          tempStorage[forwardPointer] = backwardPointer + 1;
                          forwardPointer++;
                          backwardPointer++;
                      }
                      else
                      {
                          if (backwardPointer == 0)
                          {
                              tempStorage[forwardPointer] = 0;
                              forwardPointer++;
                          }
                          else
                          {
                              int temp = tempStorage[backwardPointer];
                              backwardPointer = temp;
                          }
      
                      }
                  }
      
                  int pointer = 0;
                  int successPoints = sPattern.Length;
                  bool success = false;
                  for (int i = 0; i < sText.Length; i++)
                  {
                      if (sText[i].Equals(sPattern[pointer]))
                      {
                          pointer++;
                      }
                      else
                      {
                          if (pointer != 0)
                          {
                              int tempPointer = pointer - 1;
                              pointer = tempStorage[tempPointer];
                              i--;
                          }
                      }
      
                      if (successPoints == pointer)
                      {
                          success = true;
                      }
                  }
      
                  if (success)
                  {
                      System.Console.WriteLine("TRUE");
                  }
                  else
                  {
                      System.Console.WriteLine("FALSE");
                  }
                  System.Console.Read();
              }
          }
      }
      
      /*
       * Sample Input
      abxabcabcaby
      abcaby 
      */
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-27
        • 1970-01-01
        • 2021-10-21
        • 2019-06-07
        • 2021-07-25
        • 2011-09-08
        相关资源
        最近更新 更多