簡體   English   中英

我實施KMP算法有什么問題?

[英]What's wrong with my implementation of the KMP algorithm?

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

KMP算法有兩個階段:首先構建一個表,然后進行搜索,由表的內容指導。

朴素算法有一個階段:它進行搜索。 在最壞的情況下 ,它的搜索效率要低於KMP搜索階段。

如果KMP比天真算法慢,那可能是因為構建表所花費的時間比首先簡單地搜索字符串花費的時間長。 在短字符串上,天真的字符串匹配通常非常快。 有一個原因,我們不在字符串搜索的BCL實現中使用像KMP這樣的花式算法算法。 當您設置表格時,您可以使用朴素算法對短字符串進行六次搜索。

如果您擁有龐大的字符串並且您正在進行大量搜索以允許您重新使用已構建的表,那么KMP只是一個勝利。 您需要通過使用該表進行大量搜索來分攤構建表的巨大成本。

而且,朴素算法在奇怪和不太可能的場景中只有糟糕的表現。 大多數人都在搜索像“白金漢宮,倫敦,英國”這樣的字符串中的“倫敦”這樣的詞,而不是像“BANAN BANBAN BANBANANA BANAN BANAN BANANANANANANANANANANANANANAN ......”這樣的字符串中搜索“BANANANANANANA”等字符串。 朴素搜索算法對於第一個問題是最優的,對於后一個問題是高度次優的; 但是對前者而不是后者進行優化是有意義的。

另一種說法:如果搜索的字符串長度為w且搜索字符串的長度為n,則KMP為O(n)+ O(w)。 朴素算法是最壞情況O(nw),最好情況是O(n + w)。 但這並沒有說明“恆定因素”! KMP算法的常數因子遠大於朴素算法的常數因子。 n的值必須非常大,並且次優部分匹配的數量必須非常大,以使KMP算法贏得超快速的朴素算法。

這涉及算法復雜性問題。 您的方法也不是很好,這可能會解釋您的結果。 請記住, 一次運行代碼時,抖動必須將IL轉換為匯編代碼。 在某些情況下,這可能比運行該方法花費更長的時間 你真的應該在一個循環中運行幾十萬次代碼,丟棄第一個結果,並取其余時間的平均值。

如果你真的想知道發生了什么,你應該使用分析器來確定熱點是什么。 再次,確保您正在測量jit后運行,而不是測試代碼的運行,如果您希望結果不受jit時間的影響。

您的示例太小,並且沒有足夠的重復模式,KMP可以避免回溯。

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

一個簡單的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 
*/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM