algorithm - 为什么选择算法的运行时间是 O(n)?

标签 algorithm selection big-o

根据 Wikipedia ,基于分区的选择算法(例如 quickselect)的运行时间为 O(n) ,但我对此并不信服。谁能解释为什么是O(n)

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

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

最佳答案

有几种不同的选择算法,从更简单的快速选择(预期 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 的分配! - 但使用此分析的更强大版本,您可以说,如果您可以保证任何常数因子拆分,则完成的总功只是 n 的某个常数倍。如果我们在每次迭代中选择一个完全随机的元素(就像我们在 quickselect 中所做的那样),那么在我们最终选择数组中间 50% 的一些枢轴元素之前,我们只需要选择两个元素,这意味着,期望,在我们最终选择给出 25/75 拆分的东西之前,只需要两轮选择一个枢轴。这就是 quickselect 的 O(n) 预期运行时间的来源。

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

关于algorithm - 为什么选择算法的运行时间是 O(n)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8783408/

相关文章:

java - 查找字符串最右边的出现

python - 使用第二个数据框中的值从一个数据框中选择

java - 创建该对象的新实例时滚动浏览对象

algorithm - big-O 表示法是对算法进行最佳、最差和平均情况分析的工具吗?

algorithm - 最大子数组 - 运行时

algorithm - 如何确定具有嵌套循环的算法的计算复杂度?

r - 将矩阵次对角线转换为列 r 的高效算法

java - 如何使列中的单元格不可选择

c++ - 二叉树之字形层次顺序遍历算法的紧时间复杂度是多少?

java - 计算程序运行时间?