KMP前缀表运行时间

时间:2012-08-05 20:20:52

标签: java performance algorithm string-matching

我写了一个代码来填充KMP的前缀表。这是algorithm的小变化。我无法说服自己这个算法/​​实现在O(n)时间内运行。我很难弄清楚第二次递归调用对总运行时间的影响。有什么帮助吗?

    public void fillFailTable(int[] failTable,String p){
        failTable[failTable.length-1] = preLength(failTable,p);
    }

    private int preLength(int[] failTable,String s){

        if(s.length() == 1){
            return 0;
        }
        int n = s.length();
        int k = preLength(failTable,s.substring(0,n-1));

        failTable[n-2] = k;

        if(s.charAt(k) == s.charAt(n-1)){
            return k+1;
        }else{
            return preLength(failTable,s.substring(n-1-k));
        }
    } 

1 个答案:

答案 0 :(得分:0)

这实际上非常有趣(我仍然想知道为什么没有比我聪明的人回答这个问题)。请大家用这个解释,因为我不是100%肯定这甚至接近正确(尽管我可以100%告诉你这个方法在O(n)中运行,因为那是他们告诉我的大学几年前,但他们并没有费心去解释它,呃,所以我不得不自己想出来。

好的,让我们从s.length = 2的一个非常基本的例子开始。事先要提到两件事:

  • 在每个例子中只让我们担心最糟糕的情况,因为我们对Big Oh感兴趣,这意味着我们进入第二个preLength()方法。
  • 我们可以观察到,在寻找Big Oh时,此代码中的“k”(以及preLength()返回的值)将始终为0,您将在下面的图像中注意到这些真的很重要。

s.length == 2

我们首先输入第一个preLength()方法(让我们调用它*),现在调用s.length = 1并立即返回0.现在因为我们只考虑最坏的情况(意思是s) .charAt(k)!= s.charAt(n-1))我们输入第二个preLength(),同时还有一个长度为1的字符串(因为n = 2且k = 0)。这个也立即向我们的*返回0。这结束了我们的方法调用。总共我们有3个方法调用。我们的*和两个preLength()。这是一张图片:

enter image description here

s.length == 3

现在让我们看一个带有起始s.length = 3的示例。您可以注意到我们立即使用s.length = 2调用preLength(),并且从前面的示例中我们知道这个需要3个方法调用。现在我们需要记住,当方法preLength(2)返回此时它返回到我们的本地preLength(3),它现在将再次调用preLength(2)(else中的那个),这将再次需要3个方法调用。所以我们总共需要2 * 3 + 1个方法调用。这给了我们7.再次,这是一个图像(圆圈是preLength的调用,其长度为圆圈中的字符串):

enter image description here

结论

现在您可以看到所有这些方法调用都是对称的 - 这是因为我们的k 总是等于0,这意味着第二个preLengt()将被调用与第一个字符串大小相同的字符串 - 当我们知道自s.length = m m-1以来f(m) = 2*f(m-1)+1所需的字符数时,我们可以看到f(m)需要多少字符串{} {1}}是告诉我们为大小为m的字符串计算表所需的方法调用次数的函数。这是有效的,因为正如我之前所说的方法调用是对称的(那是因为在最坏的情况下k = 0总是和preLenght()总是返回0 ,因此2 *我们需要添加1个方法调用,我们引用的第一个。)

所以基本上随着输入的每次递增(m的大小),计算时间增加2倍加1(2 * m + 1),据我所知,这意味着这种方法在最坏的情况下是O (n)的

正如我所说的那样,请尽量采取一些措施,但我希望这是有道理的。)