根据Wikipedia,选择算法的运行时间为O(n)
,但我不相信它。任何人都可以解释为什么它是O(n)
?
在正常的快速排序中,运行时为O(n log n)
。每次我们将分支划分为两个分支(大于枢轴,小于枢轴)时,我们需要继续处理分支的两个侧,而选择算法只需要处理分支的一个侧。我完全理解这些观点。
但是如果你想到二进制搜索算法,在我们选择了中间元素之后,我们也会继续搜索分支的一个侧。算法O(1)
也是如此? 否,当然,二进制搜索算法仍然是O(log N)
而不是O(1)
。这与二进制搜索树中的搜索元素也是一样的。我们只搜索一个方面,但我们仍然会考虑O(log n)
而不是O(1)
。
有人可以解释为什么在选择算法中,如果我们继续搜索一个方面,可以考虑O(1)
而不是O(log n)
吗?对我而言,我认为算法为O(n log n)
,O(N)
用于分割,O(log n)
用于继续查找。
答案 0 :(得分:65)
有几种不同的选择算法,从更简单的快速选择(预期的O(n),最坏情况的O(n 2 ))到更复杂的中位数中值算法(Θ) (N))。这两种算法都通过使用快速分配步骤(时间O(n))来重新排列元素并将一个元素定位到其适当位置。如果该元素位于相关索引处,我们就完成了,并且可以返回该元素。否则,我们确定在哪一方进行递归并递归。
现在让我们做一个非常强大的假设 - 假设我们正在使用quickselect(随机选择枢轴),并且在每次迭代时我们设法猜测数组的确切中间位置。在这种情况下,我们的算法将如下工作:我们执行分区步骤,丢弃一半数组,然后递归处理数组的一半。这意味着在每次递归调用时,我们最终会在该级别上与数组的长度成比例地工作,但是该长度在每次迭代时保持减少两倍。如果我们计算出数学(忽略常数因子等),我们最终得到以下时间:
这意味着完成的总工作由
给出n + n / 2 + n / 4 + n / 8 + n / 16 + ... = n(1 + 1/2 + 1/4 + 1/8 + ...)
请注意,这最后一个项是n,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%中选择一些pivot元素,这意味着,在期望,在我们最终选择能够进行25/75分割的东西之前,只需要两轮选择一个支点。这是quickselect的O(n)的预期运行时间来自。
中位数中值算法的正式分析要困难得多,因为复发很困难且不易分析。直观地,该算法通过做少量工作来保证选择好的枢轴。但是,因为有两个不同的递归调用,所以像上面这样的分析将无法正常工作。您可以使用名为Akra-Bazzi theorem的高级结果,也可以使用big-O的正式定义来明确证明运行时为O(n)。有关更详细的分析,请查看Cormen,Leisserson,Rivest和Stein的“算法简介,第三版”。
希望这有帮助!
答案 1 :(得分:11)
让我试着解释选择与选择之间的区别。二元搜索。
每个步骤中的二进制搜索算法都进行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)
答案 2 :(得分:2)
在Quicksort中,递归树的深度为lg(N)级,并且每个级别都需要O(N)量的工作。所以总的运行时间是O(NlgN)。
在Quickselect中,重复树的深度为lg(N)级,每个级别仅需要其上一级的工作量的一半。这产生以下结果:
N * (1/1 + 1/2 + 1/4 + 1/8 + ...)
或
N * Summation(1/i^2)
1 < i <= lgN
这里要注意的重要一点是,我从1到lgN,但不是从1到N,也不是从1到无穷大。
总和评估为2.因此Quickselect = O(2N)。
答案 3 :(得分:0)
Quicksort没有nlogn的大O - 最坏的情况是运行时n ^ 2。
我假设您询问的是Hoare的选择算法(或快速选择),而不是O(kn)的天真选择算法。像quicksort一样,quickselect的最坏情况运行时间为O(n ^ 2)(如果选择了坏的枢轴),而不是O(n)。它可以在期望时间n运行,因为它只排序一侧,正如你所指出的那样。
答案 4 :(得分:0)
排序=重新排列元素是O(n log n),但是选择就像采用第i个元素=索引。对于链接列表或二叉树,它都是O(n)。
答案 5 :(得分:0)
因为选择,你不一定要排序。您可以简单地计算有多少具有任何给定值的项目。因此,可以通过计算每个值出现的次数来执行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位值。通过阵列“a”大约1.5遍。一旦通过整个数组计算值,再一半通过它来获得最终值。