【问题标题】:Concern about space complexity of quick sort关注快速排序的空间复杂度
【发布时间】:2020-10-22 13:40:29
【问题描述】:
def quick_sort(array):
  if len(array) <=1:
    return array

  pivot = array[-1]
  array.pop()

  less = []
  greater = []

  for num in array:
    if num > pivot:
      greater.append(num)
    else:
      less.append(num)

  return quick_sort(less) + [pivot] + quick_sort(greater)

这种快速排序实现的空间复杂度是多少?我刚刚选择了最后一个元素作为枢轴,创建了一个更大元素和更小元素的数组,并相应地移动它们。然后我递归地为更小和更大的数组做了这件事。所以最后,我将 [pivot] + [pivot] + [pivot]... 全部按排序顺序排列。现在我对空间复杂性有点困惑。我有两个较小和较大的子数组,还有递归调用堆栈。你怎么看?

【问题讨论】:

    标签: algorithm sorting data-structures computer-science quicksort


    【解决方案1】:

    这里的空间复杂度在最坏情况下为 Θ(n2),平均为 Θ(n)。

    这里是如何看待这个的。想象一下为你的算法画出完整的递归树。在任何一个时间点,算法都在这些递归调用之一中,需要存储来自该递归调用的所有数据的空间,以及用于其上方递归调用的所有空间。这是因为调用堆栈在任何一个时间点都是从某个调用返回到根调用的路径。因此,空间复杂度是从递归树中的叶子到根的任何路径上使用的最大空间量。

    然后,想象一下,您碰巧在每一步都选择了绝对最差的支点 - 比如说,您总是选择最小或最大的元素。然后你的递归树本质上是一个巨大的链表,其中根包含一个长度为 n 的数组,在它下面是一个长度为 n-1 的数组,在它下面是一个长度为 n-2 的数组,等等,直到你下来到长度为 1 的数组。空间使用量为 1+2+3+...+n,即 Θ(n2)。这不是很好。

    另一方面,假设您正在查看更“典型”的快速排序运行,其中通常会获得良好的枢轴。在这种情况下,您会期望,大约一半的时间,您会在数组的中间 50% 处获得一个枢轴。通过一些数学运算,您可以证明这意味着,按照预期,在数组大小降至其先前大小的 75% 之前,您将进行大约两次拆分。这使得递归树的深度为 O(log n)。然后,您将有大约两层大小大约为 n 的数组,大约两层大小大约为 0.75n 的数组,大约两层大小大约为 (.75)2n 等。这使得你的空间使用情况大致

    2(n + .75n + (.75)2n + ...)

    = 2n(1 + .75 + (.75)2 + ...)

    = Θ(n)。

    最后一步是因为这是一个几何级数的总和,它收敛到某个常数。

    为了提高空间使用率,您需要避免在每个级别为较小和较大的元素创建新数组。考虑使用就地分区算法来就地修改阵列。如果你很聪明,你可以使用这种方法并最终得到 O(log n) 的总空间使用量。

    希望这会有所帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-06
      • 1970-01-01
      • 2017-11-20
      • 2020-08-08
      • 1970-01-01
      • 1970-01-01
      • 2013-04-26
      • 1970-01-01
      相关资源
      最近更新 更多