我实施KMP算法有什么问题?

时间:2011-05-10 19:06:26

标签: c# algorithm performance string

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算法慢?

3 个答案:

答案 0 :(得分:22)

KMP算法有两个阶段:首先构建一个表,然后进行搜索,由表的内容指示。

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

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

如果您拥有巨大的字符串并且您正在进行批量搜索,那么KMP只会赢得一场胜利,您可以重新使用已经构建的表格。您需要通过使用该表进行大量搜索来分摊构建表的巨大成本。

而且,天真算法在奇怪和不太可能的场景中只有糟糕的表现。大多数人都在搜索像“白金汉宫,伦敦,英国”这样的字符串中的“伦敦”字样,而不是像“BANAN BANBAN BANBANANA BANAN BANAN BANANAN BANANANANANANANANANAN ......”这样的字符串中搜索像“BANANANANANANA”这样的字符串。朴素搜索算法对于第一个问题是最优的,对于后一个问题是高度次优的;但是对前者进行优化是有意义的,而不是后者。

另一种方式:如果搜索的字符串长度为w且搜索字符串的长度为n,则KMP为O(n)+ O(w)。朴素算法是最坏情况O(nw),最好情况是O(n + w)。但这并没有说明“恒定因素”! KMP算法的常数因子远大于朴素算法的常数因子。 n的值必须非常大,并且次优部分匹配的数量必须非常大,以使KMP算法赢得超快速的朴素算法。

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

如果您真的想知道发生了什么,您应该使用分析器来确定热点是什么。同样,如果你想得到的结果没有被jit时间扭曲,请确保你正在测量jit后运行,而不是测试代码的运行。

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

简单的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 
*/