KMP算法,无法理解

时间:2014-10-05 11:02:21

标签: java algorithm

我很难理解KMP算法。我理解前缀后缀是什么,我编写了代码来计算前缀后缀表:

 private int[] calculatePrefSuffArray(String pattern) {
    char patternArray[] = pattern.toCharArray();
    int tab[] = new int[pattern.length()];
    tab[0] = 0;
    int t = tab[0];
    for (int i = 1; i < pattern.length(); i++) { // t = tab[i-1]
        while(t >0 && patternArray[i] != patternArray[t] ) {
            t = tab[t-1];
        }
        if(patternArray[i] == patternArray[t]) t++;
        tab[i] = t;
    }
    return tab;
}

但我无法理解如何在KMP中使用它。有人可以帮我解释一下吗?

6 个答案:

答案 0 :(得分:2)

朴素算法

在正常模式匹配算法中,如果您有一个长度模式 m 和长度 n 的文字,将匹配模式的m个字符 通常,对于文本的n个字符,虽然你可以通过使用退出函数避免一些匹配,如果已经发生不匹配,可以避免这种行为。

因此算法的渐近复杂度为O(mn)。

但是朴素算法的问题在于它并没有试图理解关于模式的任何信息。例如,如果您在公司工作了5年,那么您可以使用5年的经验来获得优势。同样,我们可以分析子字符串并推断出一些有用的信息。这正是您在计算前缀函数时所做的事情。

KMP算法

An example KMP search


由于我们找到了与文本匹配的模式的子字符串,因此向右滑动模式 6(找到子字符串匹配的位置) - 1(位置6的前缀函数)次数不是更好并立即重新开始比较,而不是再次走向天真的方式?实际上,前缀函数值用于确定模式必须向右滑动的次数。这可能有点难以理解,但是拿一张纸并将一个小文本与一个小图案进行比较,记下所有变量的中间值,然后你就会明白这个算法有多漂亮。

这个算法的渐近复杂度是O(m + n),因为我们正在滑过 每次发生不匹配时的模式。

该算法的c ++实现如下:
程序1 :创建大小为10000000的模式并将其存储在文件inputtext.txt中(您可以更改大小。也许!)

#include<iostream>
#include<fstream>
#include<cstdlib>

using namespace std;

  main()
  {
    ofstream myfile("inputtext.txt");
    char *intxt="abcd"; // I wish the sample text to contain only these alphabets
    long int i=0;
    char* txt = new char[10000000];
    while(i<10000000)
       {
       txt[i]=intxt[(rand()%4)]; 
       i++;
       }
   myfile<<txt;
   myfile.close();
  }

Program2 :使用KMP算法进行模式搜索。

#include<iostream>
#include<string>
#include<fstream>
using namespace std;

 void prefix_fn(int* p, char* s)
 {
 s--;
 p--;
 int i,k=0;
   p[1]=0;
   for(i=2;i<=7;i++)
   {
     while ((k>0)&&(s[k+1]!=s[i]))
         k=p[k];
     if (s[k+1]==s[i])     
         k++;
     p[i]=k;
   }
 }
main()
{
int i,k;
int *p= new int[7];
char* txt=new char[10000000];
ifstream myfile("inputtext.txt");
myfile.seekg(0,ios::beg);

myfile.read(txt,10000000);
myfile.close();
char *s="abdcbab";
cout<<"Prefix Table"<<endl;
prefix_fn(p,s);
p--; // This helps to start indexing from 1, just for me to avoid confusion
s--; // Similar objective as stated above.
   for(int i=1;i<=7;i++)
      cout<<p[i]<<endl;
txt--;
k=0;
  for(i=1;i<=10000000;i++)
    {
       while(k>0 && s[k+1]!=txt[i])
           {
            k=p[k]; 
           }
       if(s[k+1]==txt[i])
           {
            k=k+1;
           }
        if(k==7)
           {
            cout<<"Match found at position : "<<i-6<<endl;
            k=p[k]; 
           }
    }
p++;
delete[] p; 
txt++;
delete[] txt; //deleting the memory allocated.
}

希望这有用。

答案 1 :(得分:1)

您的calculatePrefSuffArray("ABC ABCDAB ABCDABCDABDE")将返回此

[0  0  0  0  1  2  3  0  1  2  0  1  2  3  0  1  2  3  0  1  2  0  0]

而不是

[-1  0  0  0  1  2  3  0  1  2  0  1  2  3  0  1  2  3  0  1  2  0  0 ]

通过替换以下

来更正您的代码
tab[0] = 0;
int t = tab[0];

用这个

tab[0] = -1;
tab[1] = 0;
int t = tab[1];

传递从int[] table函数返回的已创建calculatePrefSuffArray(String pattern) 如果在true文字中找到word,它将返回string

private boolean search(String string, String word, int[] table) {
    int m = 0, i = 0;
    while ((m + i) < string.length()) {
        if (word.charAt(i) == string.charAt(m + i)) {
            if (i == word.length() - 1) {
                return true;
            }
            i++;
        } else {
            if (table[i] > -1) {
                m = m + i - table[i];
                i = table[i];
            } else {
                i = 0;
                m++;
            }
        }
    }
    return false;
}

如果您在理解代码时遇到任何问题,请告诉我。

答案 2 :(得分:0)

afzalex解释得很好。

但即使你现在还不清楚,我发现这篇文章非常简单 了解。 可能会有所帮助:http://www.algorithmwebschool.com/index.php/kmp-algorithm/

答案 3 :(得分:0)

/**
    * @function
    * Populates the _uuids array with pointers to each individual plugin instance.
    * Adds the `zfPlugin` data-attribute to programmatically created plugins to allow use of $(selector).foundation(method) calls.
    * Also fires the initialization event for each plugin, consolidating repetitive code.
    * @param {Object} plugin - an instance of a plugin, usually `this` in context.
    * @param {String} name - the name of the plugin, passed as a camelCased string.
    * @fires Plugin#init
    */
registerPlugin: function (plugin, name) {
    var pluginName = name ? hyphenate(name) : functionName(plugin.constructor).toLowerCase();
    plugin.uuid = this.GetYoDigits(6, pluginName);

    if (!plugin.$element.attr('data-' + pluginName)) {
    plugin.$element.attr('data-' + pluginName, plugin.uuid);
    }
    if (!plugin.$element.data('zfPlugin')) {
    plugin.$element.data('zfPlugin', plugin);
    }
    /**
    * Fires when the plugin has initialized.
    * @event Plugin#init
    */
    plugin.$element.trigger('init.zf.' + pluginName);

    this._uuids.push(plugin.uuid);

    return;
}

这是我用Python编写的KMP算法。 我希望能帮到你。

答案 4 :(得分:0)

要了解KMP算法,您需要了解

  1. 如何使用模式字符串构建“部分匹配表”。 This Link用非常简单的单词解释了“部分匹配表”构造技术。

  2. 如何使用上述“部分匹配表”找出“搜索文本”和“模式字符串”的下一个搜索开始位置。 This Link有一个很好的视频,详细解释了这一点。

答案 5 :(得分:0)

KMP和前缀功能几乎是同一回事。 在KMP情况下,您首先将模式与文本结合起来,然后从组合字符串生成前缀数组。当前缀函数=模式长度时,表示模式在文本内部。

  • 模式:p = ABC,len(p)= 3
  • 文本:T = XYABCYZ
  • combo = ABC_XYABCYZ(p和T用不位于其中的字符分隔 p或T,这将确保前缀功能不会超过 p的长度)
  • ABC_XYABC的前缀函数= 3,因此文本包含模式。