快速选择时间复杂度解释
Quickselect time complexity explained
来自 https://en.wikipedia.org/wiki/Quickselect 它说
"However, instead of recursing into both sides, as in quicksort, quickselect only recurses into one side – the side with the element it is searching for. This reduces the average complexity from O(n log n) to O(n), with a worst case of O(n^2)."
我不明白减少到只看一侧如何将平均复杂度降低到 O(n)?不是更多的 O(N/2 log N) 仍然是 O(N log N)。最坏的情况如何 O(n^2)
n log(n)
意味着该算法查看所有 N 项 log(n) 次。但这不是 Quickselect 的情况。
假设您正在使用 Quickselect 从 128 项列表中选择前 8 项。由于随机选择的奇迹,您选择的轴心始终位于中间点。
在第一次迭代中,该算法查看所有 128 个项目并将其分成两组,每组 64 个项目。下一次迭代分成两组,每组 32 个项目。然后是16,然后是8。检查的项目数是:
N + N/2 + N/4 + N/8 + N/16
该系列的总和永远不会达到 2*N。
最坏的情况是分区总是导致分区大小非常倾斜。考虑如果第一个分区只删除一个项目会发生什么。而第二个只删除了一个,等等。结果将是:
N + (N-1) + (N-2) ...
也就是(n^2 + n)/2)
,或者O(n^2).
一张图值一百行:
这种数列的和会无限接近1但不等于1
当我读到平均时间复杂度是 O(n) 而我们每次都将列表分成两半时(如二进制搜索或快速排序),我一开始也感到非常矛盾。为了证明只看一侧将平均运行时复杂度从O(n log n)降低到O(n),我们来对比一下quicksort(2 sided)和quickselect(1 sided)的时间复杂度递归关系。
快速排序:
T(n) = n + 2T(n/2)
= n + 2(n/2 + 2T(n/4))
= n + 2(n/2) + 4T(n/4)
= n + 2(n/2) + 4(n/4) + ... + n(n/n)
= 2^0(n/2^0) + 2^1(n/2^1) + ... + 2^log2(n)(n/2^log2(n))
= n (log2(n) + 1) (since we are adding n to itself log2 + 1 times)
快速选择:
T(n) = n + T(n/2)
= n + n/2 + T(n/4)
= n + n/2 + n/4 + ... n/n
= n(1 + 1/2 + 1/4 + ... + 1/2^log2(n))
= n (1/(1-(1/2))) = 2n (by geometric series)
我希望这能让你相信为什么只看一侧会有所不同!
它的复杂度几乎是Θ(N)(Everage O(N))
。
假设目标索引为1,这意味着我们要找到最小元素:
- 第一个循环:检查整个[1, N]和分区,近N个操作。
- 第二个循环:检查整个 [1, x] 和分区,近 N/2 个操作。
- 第三个循环:检查整个 [1, y] 和分区,近 N/2 个操作。
- ...
最终循环:检查整个[1, 1],arr[1]是我们的目标,1个操作。
因此,复杂度约为:
T = T(N + N/2 + N/4 + ... + 1)
= T(1*(1-2^(logN))*(1-2))
= T(2^(logN) - 1)
= Θ(N)
这个表达式可能太简单了,希望对你有帮助。顺便说一下,这是 quickselect 的平均复杂度,因为 quicksort/quickselect 的性能可能会因为列表值分布和目标索引而波动。您可以查看https://en.wikipedia.org/wiki/Quickselect了解更多详情。
来自 https://en.wikipedia.org/wiki/Quickselect 它说
"However, instead of recursing into both sides, as in quicksort, quickselect only recurses into one side – the side with the element it is searching for. This reduces the average complexity from O(n log n) to O(n), with a worst case of O(n^2)."
我不明白减少到只看一侧如何将平均复杂度降低到 O(n)?不是更多的 O(N/2 log N) 仍然是 O(N log N)。最坏的情况如何 O(n^2)
n log(n)
意味着该算法查看所有 N 项 log(n) 次。但这不是 Quickselect 的情况。
假设您正在使用 Quickselect 从 128 项列表中选择前 8 项。由于随机选择的奇迹,您选择的轴心始终位于中间点。
在第一次迭代中,该算法查看所有 128 个项目并将其分成两组,每组 64 个项目。下一次迭代分成两组,每组 32 个项目。然后是16,然后是8。检查的项目数是:
N + N/2 + N/4 + N/8 + N/16
该系列的总和永远不会达到 2*N。
最坏的情况是分区总是导致分区大小非常倾斜。考虑如果第一个分区只删除一个项目会发生什么。而第二个只删除了一个,等等。结果将是:
N + (N-1) + (N-2) ...
也就是(n^2 + n)/2)
,或者O(n^2).
一张图值一百行:
这种数列的和会无限接近1但不等于1
当我读到平均时间复杂度是 O(n) 而我们每次都将列表分成两半时(如二进制搜索或快速排序),我一开始也感到非常矛盾。为了证明只看一侧将平均运行时复杂度从O(n log n)降低到O(n),我们来对比一下quicksort(2 sided)和quickselect(1 sided)的时间复杂度递归关系。
快速排序:
T(n) = n + 2T(n/2)
= n + 2(n/2 + 2T(n/4))
= n + 2(n/2) + 4T(n/4)
= n + 2(n/2) + 4(n/4) + ... + n(n/n)
= 2^0(n/2^0) + 2^1(n/2^1) + ... + 2^log2(n)(n/2^log2(n))
= n (log2(n) + 1) (since we are adding n to itself log2 + 1 times)
快速选择:
T(n) = n + T(n/2)
= n + n/2 + T(n/4)
= n + n/2 + n/4 + ... n/n
= n(1 + 1/2 + 1/4 + ... + 1/2^log2(n))
= n (1/(1-(1/2))) = 2n (by geometric series)
我希望这能让你相信为什么只看一侧会有所不同!
它的复杂度几乎是Θ(N)(Everage O(N))
。
假设目标索引为1,这意味着我们要找到最小元素:
- 第一个循环:检查整个[1, N]和分区,近N个操作。
- 第二个循环:检查整个 [1, x] 和分区,近 N/2 个操作。
- 第三个循环:检查整个 [1, y] 和分区,近 N/2 个操作。
- ...
最终循环:检查整个[1, 1],arr[1]是我们的目标,1个操作。
因此,复杂度约为:
T = T(N + N/2 + N/4 + ... + 1)
= T(1*(1-2^(logN))*(1-2))
= T(2^(logN) - 1)
= Θ(N)
这个表达式可能太简单了,希望对你有帮助。顺便说一下,这是 quickselect 的平均复杂度,因为 quicksort/quickselect 的性能可能会因为列表值分布和目标索引而波动。您可以查看https://en.wikipedia.org/wiki/Quickselect了解更多详情。