【问题标题】:Why is the runtime of the Selection Algorithm O(n)?为什么选择算法的运行时间是 O(n)?
【发布时间】:2019-11-30 22:13:22
【问题描述】:

根据Wikipedia 的说法,基于分区的选择算法(例如快速选择)的运行时间为O(n),但我并不相信。谁能解释一下为什么是O(n)

在正常的快速排序中,运行时间是O(n log n)。每次我们将分支分成两个分支(大于pivot,小于pivot),我们需要在both分支中继续处理,而quickselect只需要处理一个强>分支。我完全理解这些观点。 但是,如果您认为在二分搜索算法中,在我们选择了中间元素之后,我们也只搜索了分支的 一个 侧。那么这是否使算法O(1)没有,当然,二分查找算法还是O(log N),而不是O(1)。这也与二叉搜索树中的搜索元素相同。我们只搜索one边,但我们仍然考虑O(log n)而不是O(1)

有人可以解释为什么在快速选择中,如果我们继续在枢轴的 one 一侧进行搜索,它会被认为是 O(1) 而不是 O(log n)?我认为算法是O(n log n)O(N) 用于分区,O(log n) 用于继续查找的次数。

【问题讨论】:

  • 因为O(N) + O(log N) = O(N)。当您做某事然后再做其他事情时,您对订单进行求和,而不是相乘。
  • 您链接到的维基百科页面一般是关于选择算法的,其中提到了许多算法。其中一些是 O(n) 而有些不是;你在说哪一个?另外,为什么要将快速排序算法与选择算法进行比较?它们服务于不同的目的。也许您将选择算法与selection sort 混淆了?
  • 不是做某事,然后做某事。如果我理解正确的话,就是每个 O(logn) 你做 O(n) 来划分成两侧。
  • @DavidSchwartz- OP 的问题是,为什么如果您需要对分区步骤进行 O(log N) 次迭代,每个迭代都需要 O(N),那么总共不需要O(N log N)

标签: algorithm selection big-o


【解决方案1】:

有几种不同的选择算法,从更简单的快速选择(预期 O(n),最坏情况 O(n2))到更复杂的中位数算法(Θ (n))。这两种算法都通过使用快速排序分区步骤(时间 O(n))重新排列元素并将一个元素定位到其正确位置来工作。如果该元素位于相关索引处,我们就完成了,可以返回该元素。否则,我们确定要在哪一侧递归并在那里递归。

现在让我们做一个非常强的假设 - 假设我们正在使用快速选择(随机选择枢轴)并且在每次迭代中我们设法猜测数组的确切中间位置。在这种情况下,我们的算法将像这样工作:我们执行分区步骤,丢弃数组的一半,然后递归处理数组的一半。这意味着在每次递归调用中,我们最终都会做与该级别的数组长度成正比的工作,但每次迭代时该长度都会减少两倍。如果我们计算出数学(忽略常数因素等),我们最终会得到以下时间:

  • 在第一级工作:n
  • 一次递归调用后工作:n / 2
  • 在两次递归调用后工作:n / 4
  • 在三个递归调用后工作:n / 8
  • ...

这意味着完成的总工作由下式给出

n + n / 2 + n / 4 + n / 8 + n / 16 + ... = n (1 + 1/2 + 1/4 + 1/8 + ...)

请注意,最后一项是 1、1/2、1/4、1/8 等之和的 n 倍。如果你计算出这个无限和,尽管有无限多的项,总和sum 正好是 2。这意味着总工作量是

n + n / 2 + n / 4 + n / 8 + n / 16 + ... = n (1 + 1/2 + 1/4 + 1/8 + ...) = 2n

这可能看起来很奇怪,但我们的想法是,如果我们在每个级别上进行线性工作,但继续将数组切成两半,我们最终只做了大约 2n 次工作。

这里的一个重要细节是,这里确实有 O(log n) 次不同的迭代,但并不是所有的迭代都在做相同数量的工作。事实上,每次迭代的工作量是前一次迭代的一半。如果我们忽略功正在减少的事实,您可以得出结论,功为 O(n log n),这是正确的,但不是严格限制。这种更精确的分析利用了每次迭代所做的工作不断减少的事实,给出了 O(n) 运行时间。

当然,这是一个非常乐观的假设——我们几乎永远不会得到 50/50 的比例! - 但是使用这个分析的更强大的版本,你可以说,如果你能保证any常数因子分裂,完成的总工作只是n的某个常数倍数。如果我们在每次迭代中选择一个完全随机的元素(就像我们在快速选择中所做的那样),那么在我们最终选择数组中间 50% 的某个枢轴元素之前,我们只需要选择两个元素,这意味着,在期望,在我们最终选择一个给出 25/75 分割的东西之前,只需要两轮选择一个支点。这就是 quickselect 的预期运行时间 O(n) 的来源。

中位数算法的形式分析要困难得多,因为递归很难且不易分析。直观地说,该算法通过做少量工作来保证选择一个好的枢轴。但是,由于进行了两个不同的递归调用,因此上述分析将无法正常工作。您可以使用名为Akra-Bazzi theorem 的高级结果,也可以使用 big-O 的正式定义来明确证明运行时为 O(n)。如需更详细的分析,请查看 Cormen、Leisserson、Rivest 和 Stein 的“算法简介,第三版”。

希望这会有所帮助!

【讨论】:

  • 感谢您的回答。如果我们假设我们总是进行 50/50 分割。这应该给我们 O(nlogn),这是在维基百科页面中提到的:“注意与快速排序的相似之处:就像基于最小值的选择算法是部分选择排序一样,这是部分快速排序,仅生成和分区 O( log n) 的 O(n) 个分区”。您可以在维基百科中搜索这些词。
  • @user926958- 虽然您认为有 O(log n) 次迭代是正确的,但并非所有迭代都在做相同数量的工作,实际上每次迭代所做的工作量是以前的一半。正是由于这个原因,总共 O(log n) 次迭代不会产生 O(n log n) 运行时,因为这些迭代中的每一次都比前一次迭代做的工作要少得多。总结迭代中完成的总工作,而不是计算迭代次数并乘以每个迭代所做的最大工作,给出 O(n) 答案。这有意义吗?
  • 谢谢,终于明白了!
  • 我发布了详细的平均案例分析 - 没有假设因子拆分不变 - 作为相关问题的答案:stackoverflow.com/a/25796762/1849515。这里的答案当然比数学分析更直观。
  • 感谢您的提问。 “中间 50%”是指一个元素,其值在所有值的第 25 和第 75 个百分位数之间。如果你随机选择支点,你当然不能保证在两次尝试中都能命中该范围内的东西,但由于你在每个支点选择上都有 50% 的机会,所以在你在那里找到东西之前预期的探测次数将是 2。 (这是因为概率为 p 的几何分布变量的平均值为 1/p)。
【解决方案2】:

让我试着解释一下选择和二分搜索的区别。

二分查找算法在每一步都进行 O(1) 操作。总共有 log(N) 个步骤,这使得它 O(log(N))

每个步骤中的选择算法执行 O(n) 操作。但是这个'n'每次都会减少一半。总共有 log(N) 个步骤。 这使得它 N + N/2 + N/4 + ... + 1 (log(N) 次) = 2N = O(N)

对于二分查找,它是 1 + 1 + ... (log(N) 次) = O(logN)

【讨论】:

  • N + N/2 + N/4 + ... + 1 (log(N) 次) 应该等于 O(NlogN)。您的 (N + N/2 + N/4 + ... + 1) 将大致等于 2N,因此 2N * (logN 次) 为 O(NlogN)
  • @user926958- 实际上,通过使用无限几何级数之和的公式,该和等于 2N。这就是为什么完成的总工作是 O(N) 的原因。不过,N + N + ... + N O(log N) 次的总和确实是 O(N log N)。
  • @user926958 "N + N/2 + N/4 + ... + 1 (log(N) 次) 应该等于 O(NlogN)" 是错误的,因为它是一个几何序列的算术序列。所以应该是2N左右。
【解决方案3】:

在快速排序中,递归树的深度为 lg(N) 级,每个级别都需要 O(N) 级的工作量。所以总运行时间是O(NlgN)。

在快速选择中,递归树的深度为 lg(N) 级,每个级别只需要上一层的一半工作量。这会产生以下结果:

N * (1/1 + 1/2 + 1/4 + 1/8 + ...)

N * Summation(1/i^2)
    1 < i <= lgN

这里要注意的重要一点是 i 从 1 到 lgN,但不是从 1 到 N,也不是从 1 到无穷大。

求和结果为 2。因此 Quickselect = O(2N)。

【讨论】:

    【解决方案4】:

    快速排序没有 nlogn 的大 O - 最坏情况下的运行时间是 n^2。

    我假设您询问的是 Hoare 的选择算法(或快速选择),而不是 O(kn) 的朴素选择算法。与快速排序一样,快速选择的最坏情况运行时间为 O(n^2)(如果选择了错误的枢轴),而不是 O(n)。正如您所指出的,它可以在预期时间 n 内运行,因为它只对一侧进行排序。

    【讨论】:

    • 但是,有些算法(即中位数算法)确实在最坏情况 O(n) 时间内运行。
    【解决方案5】:

    因为选择,你不一定要排序。您可以简单地计算有多少项具有任何给定值。因此,O(n) 中位数可以通过计算每个值出现的次数来执行,并选择在其上方和下方具有 50% 的项目的值。它是 1 次遍历数组,只是为数组中的每个元素增加一个计数器,所以它是 O(n)。

    例如,如果您有一个由 8 位数字组成的数组“a”,您可以执行以下操作:

    int histogram [ 256 ];
    for (i = 0; i < 256; i++)
    {
        histogram [ i ] = 0;
    }
    for (i = 0; i < numItems; i++)
    {
        histogram [ a [ i ] ]++;
    }
    i = 0;
    sum = 0;
    while (sum < (numItems / 2))
    {
        sum += histogram [ i ];
        i++;
    }
    

    最后,变量“i”将包含中位数的 8 位值。大约 1.5 次通过数组“a”。遍历整个数组计算值,再遍历一半得到最终值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-04
      • 1970-01-01
      相关资源
      最近更新 更多