Quicksort最坏情况避免策略的实用性和影响力

时间:2017-05-10 16:23:23

标签: algorithm sorting quicksort

一个简单的Quicksort的平均和最坏情况众所周知:O(n log n)和O(n ^ 2)。

为了避免最坏的情况(接近排序的数据)我遇到了一个“策略家族”,我称之为“x的中位数”或“Mox”,其中x通常是3,虽然我也看到过5。

目的是确定一个更适合处理当前分区中的值的枢轴,如果它们的排序是倾斜的。

“Mo3”中发生的情况是选择当前分区中的三个值(第一个,中间和最后一个)并进行比较以确定三个值的中位数,然后假设它们比总是选择分区中的中间值。

执行单个Mo3意味着经历n = 3的插入排序的运动,其具有2个比较的最佳情况,平均为8/3且最差为3.对于Mo5,对应值为4, 167/15和17。

正如我所看到的,这些Mox将通过在每次将它们应用于分区时将它们与总数进行比较而对Quicksort的性能产生负面影响。当然,当不再需要Mox时,可能存在截止点,可能在使用Mo3时达到3的分区大小之前,使用Mo5时分区大小为5之前。

到目前为止,我没有遇到过量化其影响的讨论。几乎就好像讨论中没有看到使用Mox所需的比较有助于它们的主机快速排序功能将产生的比较次数。

对于Mox来说,一个常见的存在理由是“让最坏的情况变得更糟”,但很少说的是他们也“使平均情况不那么好”。

所以我的问题是(比较一个基本的Quicksort和一个使用Mox的人)“不那么糟糕?”和“多少不太好?”

OOPS我使用了快速排序的平均和最差情况,应该是插入排序。正确的值是7.716667和10.

“选择随机支点”一词让我感到困惑。

如果要排序的数据是(可能是随机的)数字的向量,我猜如果你知道向量中的值的范围,那么选择一个随机数是直截了当的,这样就可以在范围内选择一个值。在我看来,对数值向量进行排序似乎是一种非现实生活场景,更适用于研究算法的基本功能。在研究之外,需要多长时间对数值向量进行排序?

如果是现实生活场景,对数据库表中的记录执行复杂排序,其中执行的排序是比较记录中的多个字段,例如邮政编码,性别,出生日期等?那么“随机枢轴”会对应表中的随机物理记录吗?

1 个答案:

答案 0 :(得分:0)

总的来说,选择一个支点的基本策略应该是选择随机元素。当然,这种策略的成本取决于生成随机数的成本(每个分区一个,平均O(N)次),但不同于任何涉及从固定位置挑选枢轴的策略,随机策略不开放DoS攻击(假设随机数发生器不对预测攻击开放)。

始终选择分区的第一个(或最后一个)元素不仅对DoS攻击开放;它还会导致已排序数据的最坏情况行为。在任何可以预期排序或部分排序的输入的正常环境中,这肯定是不可接受的,因此应该从顶部拒绝。

3中位数(或 k 中位数)策略,其中计算中位数的对象位于固定位置,受到DoS攻击。在可能的环境中,可以通过随机选择一个或多个值来参与中值计算来改进策略。我相信(虽然我不打算提供任何猜想的证据),选择"中间"随机值,尽管随机选择所有三个值可能会有一些微小的好处。但是,额外的成本可能很重要:两个(更多)随机数和两个额外的互换。 (如果您使用 k 的中位数,则将 k -1替换为2。)

以下比较是基于上述假设 - 我们将拒绝将枢轴作为第一个元素的策略,并且我们可以使用第一个,最后一个和中位数的中位数做3的中位数。单个随机选择的元素。由于这两个都为每个分区选择一个随机数,因此我忽略了生成随机数的成本。我还将 n 一致地用作分区的大小,并假设它至少为4,因此找到中位数的三个元素没有问题。 (通常,现实世界的QS实现对小分区使用不同的排序算法;我认为可以公平地假设对于大小小于某个小数量的分区,存在硬编码排序函数。)

在随机支点策略中,我们按如下方式进行:

  1. 在半开放范围[0, n ]中生成随机索引 r

  2. 交换元素0和 r 。元素0将是透视值。

  3. 对剩余的 n -1元素进行分区,其中包括将每个元素与透视值进行一次比较,总计 n -1比较。交换次数难以估计(并且随分区算法而变化)但肯定是O( n )。

  4. 在随机三元中值策略中,我们将首先安排位置0,1和 n -1的元素按顺序排序。所以程序将是:

    1. 在半开范围内生成随机索引 r [1, n -1)

    2. 比较位置0和 n -1的元素;如有必要,交换它们以使它们整理好。 (1比较;最坏情况1交换)

    3. 将元素0与元素 r 进行比较。如果元素 r 较小,则旋转元素0,1和 r 。否则,将元素 r 与元素 n -1进行比较,如果元素 r 更大,则旋转元素 n -1 ,1和 r 。 (更糟糕的情况:2比较,1旋转。)枢轴值现在是位置1的值。

    4. 使用相同的分区算法,但这次涉及 n -3个元素,因为元素0和 n -1已经正确放置。这涉及 n -3比较和O( n )交换。

    5. 现在,比较两种策略。

      • 在这两种情况下,步骤1的成本是相同的,因为生成随机数的成本并不取决于可能性的范围(至少,给定这两个可能的范围)。

        < / LI>
      • 第一个策略中第2步的成本是零比较和一个交换。在第二策略中组合的步骤2和3的成本是两个或三个比较(预期8/3),零或一个交换(预期1/2)和零或一个旋转(预期2/3)。最坏情况的差异是三个比较和一个旋转;预期成本的差异是8/3比较和2/3的旋转少1/2交换。

        (顺便说一句,交换是三个突变 - t = a; a = b; b = t - 旋转是四个突变 - t = a; a = b; b = c; c = t。如果我们将旋转近似为交换的4/3,最坏的情况差异是交换的4/3,预期的情况是交换的7/18。)

      • 第一个策略的步骤3和第二个策略的步骤4之间的区别在于相反的方向:随机中值-3使用2个较少的比较,O(1)个掉期较少。

      因此,加上所有这些,我们可以估计随机中位数为3的每分区成本为最差的一个比较,平均为2/3的比较。 (我不完全确定如何比较交换计数,但我认为在最坏的情况下,随机中位数为3,在平均情况下大致相等。)

      现在,问题是:随机中位数3是否提供足够的好处,超过每个分区的比较的2/3?它肯定不会花费很多好处来证明这个成本是合理的,但是我有点怀疑它甚至提供了那么多。

      如果我们使用5的中位数而不是3的中位数,那么成本/收益会更加偏离。 5的中位数在分区阶段保存了另外两个比较(因为剩下的子阵列现在 n -5),但计算中位数的成本略高。然而,它没有OP所建议的那么高:可以使用{{中描述的众所周知的算法 - 按最差情况七次比较(预期数量仅略微小于7)顺序排列五个元素。 3}}。但即使成本为7(而不是OP中建议的平均11.17 /最差情况17),它仍然比分区阶段中四次比较的节省要大得多。

      所以我得出结论,随机中位数3 可能稍微好些,但我怀疑不足以证明额外的代码复杂性,但随机中位数为5肯定不是。