对几乎排序的数组进行排序(错误放置的元素不超过k)

时间:2010-04-28 04:21:04

标签: arrays algorithm sorting

我最近被问到这个面试问题:

  

您将获得一个几乎已排序的数组,因为每个N元素可能会被错误地放置在正确排序顺序中不超过k个位置。找到一种节省空间和时间的算法来对数组进行排序。

我有一个O(N log k)解决方案如下。

我们将arr[0..n)表示从索引0(包括)到N(不包括)的数组元素。

  • 排序arr[0..2k)
    • 现在我们知道arr[0..k)处于最终排序位置......
    • ...但arr[k..2k)可能仍然错位k
  • 排序arr[k..3k)
    • 现在我们知道arr[k..2k)处于最终排序位置......
    • ...但arr[2k..3k)
    • 可能仍会放错k
  • 排序arr[2k..4k)
  • ....
  • 直到你排序arr[ik..N),然后你就完成了!
    • 当您剩下少于2k个元素时,最后一步可能比其他步骤便宜

在每个步骤中,您对2k中的O(k log k)个元素进行排序,并在每个步骤结束时将至少k个元素放在最终的排序位置。有O(N/k)个步骤,因此整体复杂度为O(N log k)

我的问题是:

  • O(N log k)最佳吗?这可以改进吗?
  • 您可以在没有(部分)重新排序相同元素的情况下执行此操作吗?

5 个答案:

答案 0 :(得分:36)

正如Bob Sedgewick在他的论文工作(和后续工作)中所示,插入排序绝对粉碎“几乎排序的数组”。在这种情况下,你的渐近线看起来很好但是如果k < 12我打赌插入排序每次都获胜。我不知道为为什么插入排序做得很好有一个很好的解释,但是看看的地方将是Sedgewick的教科书中的一个名为算法(他已经完成了许多不同语言版本。)

  • 我不知道O(N log k)是否是最优的,但更重要的是,我并不在乎 - 如果k很小,那么重要的是常数因子,如果k很大你也可以对数组进行排序。

  • 插入排序会解决此问题,而无需重新排序相同的元素。

Big-O表示法对于算法类来说非常好,但在现实世界中,常量很重要。很容易忽视这一点。 (我说这是教授Big-O符号的教授!)

答案 1 :(得分:19)

如果仅使用比较模型,则O(n log k)是最佳的。考虑k = n时的情况。

要回答您的其他问题,是的,可以通过使用堆来进行排序。

使用2k元素的最小堆。首先插入2k元素,然后删除min,插入下一个元素等。

这保证了O(n log k)时间和O(k)空间,而堆通常具有足够小的隐藏常量。

答案 2 :(得分:7)

如果k足够大,那么您的解决方案很好。在时间复杂性方面没有更好的解决方案; k个地方的每个元素可能不合适,这意味着您需要学习log2 k个信息才能正确放置,这意味着您至少需要进行log2 k次比较 - 所以它必须是至少O(N log k)的复杂性。

然而,正如其他人所指出的那样,如果k很小,那么常数术语就会杀了你。在这种情况下,使用每次操作非常快的东西,比如插入排序。

如果你真的想要达到最佳状态,你可以实现这两种方法,并根据k从一种方法切换到另一种方法。

答案 3 :(得分:7)

已经指出,其中一个渐近最优解决方案使用最小堆,我只是想用Java提供代码:

body{
background-color: whitesmoke;
background-image: url("https://static.pexels.com/photos/1562/italian-landscape-mountains-nature.jpg");
background-repeat: no-repeat;
background-size: 1400px 750px;}

答案 4 :(得分:6)

由于k显然应该非常小,因此插入排序可能是最明显且普遍接受的算法。

在随机元素的插入排序中,您必须扫描N个元素,并且必须将每个元素平均移动N / 2个位置,从而总共运算~N * N / 2个。在大O(或类似)特征中忽略“/ 2”常数,给出O(N 2 )复杂度。

在您提议的情况下,预期的操作次数为~N * K / 2 - 但由于k是常量,因此整个k/2项将被忽略。 O表征,因此总体复杂度为O(N)。