【问题标题】:Is Quicksort in-place or not? [duplicate]Quicksort 是否就地? [复制]
【发布时间】:2014-03-28 11:56:45
【问题描述】:

所以快速排序的空间效率是O(log(n))。这是维护调用堆栈所需的空间。

现在,根据Wikipedia page on Quicksort,这符合就地算法,因为该算法只是交换输入数据结构中的元素。

According to this page 然而,O(log n) 的空间效率使快速排序无法到位,因为它的空间效率大于 O(1)。根据这个定义,任何空间效率大于 O(1) 的算法都不是就地的。所以我假设这意味着所有递归算法都没有按定义到位?

显然这里有两种不同的就地定义。维基百科并不总是一个完全可靠的来源,所以我咨询了我的一位教授。

我的教授同意第二个定义。他说 Quicksort 没有到位。即使数据保留在输入数组中,堆栈所需的额外空间也会使其不合格。我的教授写了一本关于算法的流行书,所以我非常尊重他的意见,但这个答案对我来说似乎并不正确。

我认为 in-place 的属性是非常真实的。数据保持不变。它不会脱离其原始数据结构。对我来说,这个定义更有用,因为它意味着您可以使用指针执行算法,而不是要求您复制数据。这似乎是一种算法的宝贵属性,名副其实。

【问题讨论】:

  • 这个问题让我想起了我最近读到的关于鸭子类型和强类型的模糊含义。我们可以知道快速排序的好处,而无需将其标记为就地或非就地。我想我是在争论为什么重要的观点。 耸耸肩
  • 数据没有留在原地。它以调用快速排序的索引的形式泄漏到堆栈。
  • 相关,一个可以根据所有定义编写一个高度修改的快速排序版本。但是,关于天气是否存在一些争论,目前它仍然是一种快速排序。 ideone.com/cv6VsT
  • 另一种观点是,对于较大的 N 值,log(N) 实际上是 O(1)。我在这里只是部分开玩笑——因为 10^100,大约是 @ 987654325@,远远大于宇宙中亚原子粒子的估计数量,我觉得对于任何现实的排序问题,log(N) 都以常数 300 为界。

标签: algorithm computer-science quicksort in-place space-efficiency


【解决方案1】:
来自 MIT Press 的

Intro to Algorithms 将 QuickSort 限定为 in-place - 它对数组内的元素进行排序,最多在数组外的任意一个恒定数量的元素给定时间。

归根结底,人们总会有不同的意见(自上而下的记忆是否被认为是动态编程?对一些“经典”人来说不是),站在你最信任的一方。我信任 MIT Press 的编辑和作者(以及我的教授,他认为它是就地的)。

通常,QuickSort 的问题不在于它不能就地排序,而在于它不是稳定 - 卫星数据没有按顺序排列。

编辑

Kuroi 的观点突出了我认为非常重要的部分论点。

许多人认为递归调用需要 O(log(n)) 额外内存,并且数据以堆栈索引的形式泄漏,但是对于非常大的 N (log(1,000,000,000) =~ 30) 并且它忽略了一个事实,即当 size(data) >> size(index) 时,通常在堆上移动数据需要更长的时间。

数据的索引不是元素本身 - 因此数据的元素不存储在数组之外,除了它们的恒定数量(在每次调用中)。

【讨论】:

  • 很多人认为快速排序不是就地排序,因为它需要 O(log n) 个额外的数组之外的 data
  • "但是对于非常大的 N (log(1,000,000,000) =~ 30),这可以忽略不计" 该语句完全破坏了标准算法表示法的全部意义,即称其为 O(log n) 开始和。如果可以忽略不计,那么快速排序就是一个 O(n) 算法!
  • @MooingDuck 我的观点是相关的成本不是访问相对较小的堆栈上的索引
  • 在渐近分析中,您不能只忽略对数项!无论如何,最坏情况的复杂性是 $\Theta(n)$ 级别的递归。最后,如果移动实际数据项的成本很高,请不要移动它们;而是将指针移动到它们。这是标准的,因此我认为您无法区分元素及其索引。
  • 我认为这比重复问题中的一个更好的答案,因为它提供了两种观点,而不仅仅是一种观点。
【解决方案2】:

严格来说,快速排序的空间效率为 O(n),因为退化的情况需要在堆栈上为数组的每个元素建立一个索引。虽然平均而言它将是 O(log(n))。鉴于我认为没有任何方法可以将其称为“就地”算法,除非您使用“就地”的退化定义,这意味着原始数据未存储在原始数组边界之外(不包括复制/交换操作)。

这种“就地”的定义将是退化的,因为您可以采用任何“不合时宜”的算法,并通过让它完成所有额外数据存储使用指向原始数组的指针来满足这种“就地”要求.然后,当找到答案时,您可以使用指针数据“就地”重新排序原始数组。

【讨论】:

  • 可以确保快速排序在最坏的情况下使用 O(log n) 空间,方法是始终在元素最多的分支之前执行元素较少的分支。
  • 因为快速排序选择一个元素将数组拆分为两部分,所以有可能(尽管不太可能)选择拆分数组的元素总是将其拆分为一个空集和一组剩余的元素。这种最坏情况的行为将​​导致 O(n) 的最坏情况时间和空间效率。
  • 我见过的大多数快速排序实现都会在对包含全部相等元素的数组进行排序时出现这种行为。这是因为大多数快速排序不会花时间在等于拆分元素的两个集合中平均划分元素。
  • “这种最坏的情况会导致”当您首先处理空数组然后(非递归地)继续处理另一部分时,不需要额外的内存。所以这实际上是最好的情况w.r.t。记忆。但我同意许多实现都是愚蠢的。
【解决方案3】:

qsort 确实在原地交换数据,但用于递归的堆栈空间在 log2(N) 中。

这两个属性并不矛盾。通常“就地”指的是堆内存,即您必须明确分配给算法工作的内存。

但是,对数空间复杂度基本上可以忽略不计,除非在病态情况下(例如,您想在具有 512 字节堆栈的 8 位微控制器上进行快速排序:))。

【讨论】:

  • 栈内存怎么没有显式分配?就图灵机而言,“堆栈”和“堆”有什么区别?虽然我同意在那种特殊情况下它并不重要,但从理论的角度来看它仍然很重要。 =)
  • 呸,假设我更倾向于考虑实际的观点,那么。如果您的堆栈可以处理 1000 个项目,那么您只需要两倍的大小来处理 100 万个。这使得 o(log(N)) 对于所有实际目的都可以忽略不计。而且由于 qsort 是一种终端算法(它不会调用任何更复杂的处理,除非你做了非常奇怪的事情来比较两个对象),o(log(N)) 消耗将被任何更高阶的东西所掩盖。跨度>
  • @kuroi:使用 Big-O 表示法但不使用适当的计算模型是荒谬的。在 RAM 模型中,堆和堆栈之间没有区别,而且我知道没有其他模型存在。
  • @NiklasB 如果你这么说...
【解决方案4】:

这一切都取决于“就地”算法的定义。

如果您将“就地”定义为需要恒定数量的内存,则快速排序不是“就地”,因为它需要 log(N) 内存进行递归。

如果您将“就地”定义为更人性化的“不会将数据移动到输入结构之外”,那么快速排序又不是“就地”。数据以调用快速排序方法的索引的形式泄漏到内存,这是算法工作所必需的。这个额外内存的内容直接依赖输入,怎么不泄露?

如果您将“就地”定义为不复制,那么寻找数组总和的愚蠢算法怎么样:创建另一个长度为 (n - 1) 的数组,其中包含 b[i] = a[ i + 1] + a[0] / n。这有点复制,虽然内容不同,但这个额外内存的内容是输入数据的函数,就像快速排序算法中堆栈上的索引一样。

我认为“就地”的维基百科定义是最有用的,根据它,快速排序不是“就地”,因为它使用非常量的内存。

【讨论】:

  • 我明白了。我认为我的困惑源于就地的正式定义和就地的随意定义之间的差异。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-15
  • 1970-01-01
  • 2012-02-24
  • 2015-09-03
  • 2013-07-27
  • 2021-10-25
相关资源
最近更新 更多