简体   繁体   English

python O(n log n)中算法的时间复杂度

[英]Time complexity of an algorithm in python O(n log n)

Given a list, say 'x' is in a length of n, what's the time complexity of the following algorithm? 给定一个列表,假设“ x”的长度为n,那么以下算法的时间复杂度是多少?

def foo(x):
  n = len(x)
  if n <= 1:
     return 17
  return foo(x[:n//2]) + foo(x[n//2:])

The answer is: 答案是:

O(n log n) O(n log n)

But I can't figure out why? 但是我不知道为什么吗? I struggle to figure the last row where we use recursion, I know that it's cutting the length of the list in half each time so its O(log n), but it add's to each iteration the other recursion which is also O(log n) each time so I though its O(log n log n) but unfortunately its not. 我很难弄清楚我们使用递归的最后一行,我知道每次它将列表的长度减少一半,所以它的O(log n),但是它在每次迭代中都添加了另一个递归,它也是O(log n) ),因此我虽然使用了O(log n log n),但不幸的是没有。

You are correct in identifying that it's O(log n), but you fail to identify what it is. 您可以正确地识别出它是 O(log n),但无法识别出是什么。 it is the number of steps it takes to reach the base case. 是达到基本情况所需的步骤数。 Since each time you are cutting the list in half, each time you call foo , you are working with a list which is half the size of the one you just had. 由于每次将列表切成两半,因此每次调用foo ,您使用的列表的大小是您刚拥有的列表的一半。 Therefore, it takes O(log n) steps to reach the base case. 因此,需要O(log n)步才能达到基本情况。

The next question is: how much work is done at each step? 下一个问题是:每个步骤要完成多少工作? In the first step, the list is broken in half, which requires n memory copies. 第一步,将列表分成两半,这需要n内存副本。 In the second step, two lists of size n/2 are broken in half. 在第二步中,将大小为n/2 两个列表分成两半。 The amount of work done remains the same! 完成的工作量保持不变! From one step to the next, the size of each list you are cutting halves (due to calling foo(n//2) ), but the number of lists you must do this for doubles (since you are calling foo twice recursively). 从一个步骤到下一个步骤,每个列表的大小都减少了一半 (由于调用foo(n//2) ),但是您必须将此列表的数量加倍 (因为您递归调用foo两次)。 Therefore, for each step, you are always doing O(n) work. 因此,对于每一步,您总是在做O(n)工作。

O(log n) steps * O(n) work at each step = O(n log n) in total. O(log n)个步骤*每个步骤的O(n)工作量=总O(n log n)。

This is similar to merge sort. 这类似于合并排序。 Here you take O(n) time to slice the array as seen here and then you do operations on both halves of the list. 在这里,您需要O(n)时间对数组进行切片, 如此处所示 ,然后对列表的两半进行操作。 Time complexity of merge sort is O(n log(n)). 合并排序的时间复杂度为O(n log(n))。

If you want derivation of merge sort you can take a look at this 如果你想合并的推导排序,你可以看看这个

This algorithm returns once for each element in the list O(n) It then also implements a bisection search O(log n) Therefore, it's O(n log n) 该算法为列表O(n)中的每个元素返回一次,然后还实现了对分搜索O(log n)因此,它为O(n log n)

But I can't figure out why? 但是我不知道为什么吗? I struggle to figure the last row where we use recursion, I know that it's cutting the length of the list in half each time so its O(log n), but it add's to each iteration the other recursion which is also O(log n) each time so I though its O(log n log n) but unfortunately its not. 我很难弄清楚我们使用递归的最后一行,我知道每次它将列表的长度减少一半,所以它的O(log n),但是它在每次迭代中都添加了另一个递归,它也是O(log n) ),因此我虽然使用了O(log n log n),但不幸的是没有。

O(log n) + O(log n) = O(log n) O(log n) + O(log n) = O(log n)

Adding a print statement or two to this should help a lot: 为此添加一两个打印语句将有很大帮助:

def foo(x):
    n = len(x)
    print(x)
    if n <= 1:
        print("return")    
        return 17
    return foo(x[:n//2]) + foo(x[n//2:])

>>> foo([1,2,3,4,5,6,7,8])
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4]
[1, 2]
[1]
Return
[2]
Return
[3, 4]
[3]
Return
[4]
Return
[5, 6, 7, 8]
[5, 6]
[5]
Return
[6]
Return
[7, 8]
[7]
Return
[8]
Return

This is obviously returning once for each element in the list, which makes it at least O(n) . 对于列表中的每个元素,显然返回一次,这使其至少为O(n) Additionally, to split the list in a bisection search type approach takes O(log n) 此外,以二等分搜索类型的方法拆分列表需要O(log n)

def foo(x):
   n = len(x) # outer
   if n <= 1: # inner
     return 17 # inner
   return foo(x[:n//2]) + foo(x[n//2:]) #outer

we can split the function into 2 parts. 我们可以将功能分为两部分。 The first, outer part can be defined with "n=len(x)" and "return foo(x[:n//2]) + foo(x[n//2:])" in which "x" is divided into 2 recursively. 第一个外部部分可以用“ n = len(x)”和“ return foo(x [:n // 2])+ foo(x [n // 2:])”定义,其中“ x”是递归分为2个。 Thus, the outer function was log n. 因此,外部函数为log n。 In the second, inner part is composed of "if n <= 1: \\ return 17" where n is searched with "n <= 1". 在第二部分中,内部部分由“ if n <= 1:\\ return 17”组成,其中n用“ n <= 1”搜索。 therefore inner part function is just n. 因此内部函数只是n。 The inner part x the outer part give us "n.log n " as a result. 结果,内部x外部给出了“ n.log n”。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM