了解最长公共子序列算法的时间复杂度

时间:2016-01-08 23:53:24

标签: algorithm recursion lcs subsequence

我不理解最长公共子序列算法的递归函数的O(2^n)复杂度。

通常情况下,我可以将这种表示法与算法的基本操作数量(在这种情况下进行比较)联系起来,但这一次在我看来并没有意义。

例如,有两个长度相同5的字符串。在最坏的情况下,递归函数计算251比较。并且2^5甚至不接近该值。

任何人都可以解释这个函数的算法复杂性吗?

def lcs(xstr, ystr):
    global nComp
    if not xstr or not ystr:
        return ""
    x, xs, y, ys = xstr[0], xstr[1:], ystr[0], ystr[1:]
    nComp += 1
    #print("comparing",x,"with",y)
    if x == y:
        return x + lcs(xs, ys)
    else:
        return max(lcs(xstr, ys), lcs(xs, ystr), key=len)

2 个答案:

答案 0 :(得分:3)

要正确理解它,请仔细查看图表,并在阅读图表时遵循递归的自上而下方法。

Here, xstr = "ABCD"
      ystr = "BAEC"

                                    lcs("ABCD", "BAEC")       // Here x != y 
                                  /                     \  
                lcs("BCD", "BAEC")   <--  x==y   -->    lcs("ABCD", "AEC")  x==y
                          |                                        |
                          |                                        |
                  lcs("CD", "AEC")   <--  x!=y   -->     lcs("BCD", "EC")
                 /                \                     /                \
                /                  \                   /                  \
               /                    \                 /                    \
      lcs("D","AEC")                  lcs("CD", "EC")              lcs("BCD", "C")
    /                \              /               \              /        \       
lcs("", "AEC")        lcs("D","EC")                  lcs("CD", "C")        lcs("BCD","")
  |        \         /              \                       |             /       |
Return     lcs("", "EC")    lcs("D" ,"C")            lcs("D", "")   lcs("CD","")  Return
           /         \       /         \             /        \       /        \ 
        Return      lcs("","C")    lcs("D","") lcs("","")  Return  lcs("D","")  Return
                     /     \         /     \      /                 /      \
                  Return   lcs("","")       Return            lcs("", "")  Return
                                 |                                  |
                              Return                            Return

注意:递归调用的正确表示方式通常是使用树方法完成的,但是在这里我使用图形方法来压缩树,这样就可以很容易地理解递归调用了走。当然,我很容易代表。

  • 由于在上图中有一些冗余对,例如lcs("CD", "EC"),这是从"A" "AEC"删除lcs("CD", "AEC")的结果来自"B""BCD"的{​​{1}}。因此,执行时会多次调用这些对,这会增加程序的时间复杂度。

  • 您可以很容易地看到,每个对都会为其下一个级别生成两个结果,直到遇到任何字符串或lcs("BCD", "EC")。因此,如果字符串的长度为n,则m (考虑到xstr的长度为x==y且ystr为n,我们正在考虑最坏的情况)。然后,我们将在订单末尾有数字结果: 2 n + m 。 (怎么样?想)

因为 n + m 是一个整数,所以说 N 。因此,算法的时间复杂度: O(2 N ,对于 N 的较大值无效。

因此,我们更喜欢动态编程方法而不是递归方法。它可以将时间复杂度降低到: O(n.m) =&gt; O(n 2 ,当n == m时。

即使是现在,如果你很难理解逻辑,我建议你制作一个m (不是我在这里展示的图表)代表{{ 1}}和tree-like。我希望你能理解它。

任何疑问,评论最受欢迎。

答案 1 :(得分:1)

O(2^n)表示足够大 (2^n)比例与<{1}}的运行时间。这并不意味着数字是坏的,高的,低的或任何特定的 n,并且它没有提供计算绝对运行时的方法。 / p>

要了解暗示,您应该考虑n = 1000,2000,3000甚至100万,200万等的运行时间。

在您的示例中,假设对于n = 5,算法最多需要251次迭代,那么n预测是n = 50的预测,将在 O(n) = 2^(50)/2^(5)*251 = 2^45*251次迭代。