python - 这种快速排序实现的空间复杂度
问题描述
这个问题很笼统,但也有问题:
def quick_sort(lst):
if len(lst) < 2: return lst
pivot_lst = lst[0]
left_side = [el for el in lst[1:] if el < pivot_lst]
right_side = [el for el in lst[1:] if el >= pivot_lst]
return quick_sort(left_side) + [pivot_lst] + quick_sort(right_side)
时间复杂度:O(nlog(n))
预期,O(n^2)
最坏情况
空间复杂度:???
因此,对于预期的时间复杂度,最好的情况是何时left
和right
均分,以下系列将适用于 n 大小的输入:
n + n/2 + n/4 + n/8 +... +1
= n(1 + 1/2 + 1/4 + 1/8 + 1/16 + 1/32 + ... . )
= O(n)
It follows that in the worst case, which occurs when the pivot point selected is the largest or smallest value in the list, this would apply:
n + (n-1) + (n-2) +... + 1
= (n^2 + n) / 2
= O(n^2)
我的问题是,上面的系列是否分别代表 和 的预期和最差空间复杂O(n)
性O(n^2)
?
我正在为堆栈帧内存如何在这里发挥作用而苦苦挣扎。我们只是添加它吗?
所以,如果它O(log(n))
,那么空间复杂度是O(n) + O(log(n)) -> O(n)
或者它与辅助数据的关系是什么?
我是否可以得出结论,当辅助数据结构和递归堆栈都存在时,我们只需要计算两者中的较大者吗?
解决方案
概括
在这个快速排序的实现中,是的——预期的辅助空间复杂度是O(n)
,最坏情况下的辅助空间复杂度是O(n^2)
。
我正在为堆栈帧内存如何在这里发挥作用而苦苦挣扎。我们只是添加它吗?
所以,如果它的 O(log(n)),那么空间复杂度是 O(n) + O(log(n)) -> O(n)
[...]
我是否可以得出结论,当辅助数据结构和递归堆栈都存在时,我们只需要计算两者中的较大者吗?
不。
我认为您正确地注意到递归堆栈深度在O(log(n))
预期的情况下,但错误地认为这意味着它的空间复杂度也在O(log(n))
预期的情况下。这不一定是真的。
- 一个单独的堆栈帧可以表示比 更多的空间
O(1)
。 - 一帧代表多少空间可能因帧而异。
因此,在求算法的总空间复杂度时,不能将其递归深度与其数据需求分开分析,然后在最后将两者相加。你需要一起分析它们。
一般来说,您需要了解:
- 递归的深度——会有多少堆栈帧。
- 对于这些堆栈帧中的每一个,它的空间复杂度是多少。这包括函数参数、局部变量等。
然后,您可以将同时处于活动状态的所有堆栈帧的空间复杂度相加。
示例:预期案例
想象一下这个函数调用树n=8
。我使用该符号表示“使用元素quick_sort(n)
列表进行快速排序”。n
quick_sort(8)
quick_sort(4)
quick_sort(2)
quick_sort(1)
quick_sort(1)
quick_sort(2)
quick_sort(1)
quick_sort(1)
quick_sort(4)
quick_sort(2)
quick_sort(1)
quick_sort(1)
quick_sort(2)
quick_sort(1)
quick_sort(1)
由于您的实现是单线程的,因此一次只有一个分支处于活动状态。在最深处,它看起来像:
quick_sort(8)
quick_sort(4)
quick_sort(2)
quick_sort(1)
或者,一般来说:
quick_sort(n)
quick_sort(n/2)
quick_sort(n/4)
...
quick_sort(1)
让我们看看每一帧会占用多少空间。
<calling function>
lst: O(n)
quick_sort(n)
lst: O(1)
pivot_lst: O(1)
left_side: O(n/2)
right_side: O(n/2)
quick_sort(n/2)
lst: O(1)
pivot_lst: O(1)
left_side: O(n/4)
right_side: O(n/4)
quick_sort(n/4)
lst: O(1)
pivot_lst: O(1)
left_side: O(n/8)
right_side: O(n/8)
...
quick_sort(1)
lst: O(1)
请注意,我正在考虑lst
始终具有空间复杂度的参数O(1)
来反映 Python 列表是按引用传递的。如果我们把它做成O(n)
,O(n/2)
等等,我们就会重复计算它,因为它实际上和调用函数的left_side
or是同一个对象right_side
。这最终不会影响此特定算法的最终结果,但通常您需要牢记这一点。
我在符号上也很草率。写作O(n/2)
让人很想立即将其简化为O(n)
. 不要那样做:如果你这样做,你最终会夸大总空间复杂度。
简化一点:
<calling function>
lst: O(n)
quick_sort(n)
everything: O(n/2)
quick_sort(n/2)
everything: O(n/4)
quick_sort(n/4)
everything: O(n/8)
...
quick_sort(1)
everything: O(1)
把它们加起来:
O(n) + O(n/2) + O(n/4) + O(n/8) + ... + O(1)
= O(n)
示例:最坏情况
使用与上述相同的方法,但为简洁起见跳过了一些步骤:
<calling function>
lst: O(n)
quick_sort(n)
everything: O(n-1)
quick_sort(n-1)
everything: O(n-2)
quick_sort(n-2)
everything: O(n-3)
...
quick_sort(1)
everything: O(1)
O(n) + O(n-1) + O(n-2) + O(n-3) + ... + O(1)
= O(n^2)
推荐阅读
- sqlite - 谁使用 Dismissible 从数据库中删除任务,使用颤振和 sqlite?
- bash - 在 bash 中排名时处理平局
- c++ - 纯虚类(A)派生的指针不能访问纯类(B)的重载方法
- r - 检测具有缺失值的前几行的变化 - 加速循环 - R
- arrays - 为什么 VBA 会提前退出我的 for 循环,为什么单元格值没有正确存储在我的数组中?
- python - 无法使用 gnupg 导入 pgp 密钥
- charts - Highcharts 使用 CSV 文件中的数据显示区域范围和折线图
- python - 具有多个 args 和 kwargs 的函数的 Multiprocessing.pool
- javascript - 如何在其他输入标签onkeyup和onchange上输入值并计算货币的汇率
- javascript - Material-UI AppBar 按钮在较小的屏幕上相互折叠