我有一个名为quickSort()的函数就是这样做的。我试图理解“小”的行为。并且'更大'清单(见下文)。具体来说,在函数中使用递归时,Python如何处理内存?每次调用该函数时,请执行“较小的”操作。并且'更大'列表被覆盖(我假设发生这种情况......)?如果是这样,额外的内存将在何处释放,因为每个函数执行时列表都会变小......
def quickSort(lst):
if len(lst) <= 1:
return lst
smaller = [x for x in lst[1:] if x < lst[0]]
larger = [x for x in lst[1:] if x >= lst[0]]
return quickSort(smaller) + [lst[0]] + quickSort(larger)
创建的两个列表究竟发生了什么,以及在此方案中使用就地排序是否有任何好处。
免责声明:我一般是Python和算法的新手,详细解释将不胜感激。提前致谢。
编辑:
以下是重新提出的问题:
从初始列表开始,[8,5,2,9,1,7,3,4,6]
quickSort#1生成3个列表:[5,2,1,7,3,4,6] , [8] , [9]
quickSort#2产生3个列表:[2,1,3,4] , [5] , [6,7] (plus [8] , [9])
quickSort#3产生5个列表:[1] , [2] , [3,4] and [6] , [7] (plus [8] , [9])
quickSort#4生成1个列表:[1] , [2] , [3] , [4] , [5] , [6] , [7] , [8] ,[9]
它们都在此过程中连接在一起。我想知道的是列表[8,5,2,9,1,7,3,4,6] , [5,2,1,7,3,4,6] , [8]
等是否存储,直到程序退出。 (上面的实现可能不正确,我只想说明我的观点)。
答案 0 :(得分:2)
快速排序 O(nlogn),它会不断将问题分成两半,因为您将看到此问题的作用。
假设列表 x 并且调用quickSort(x)
x = [6,12,4,5,2,5,14,23,1,5]
quickSort(x)
这是一个递归函数,从内部它将再次调用该函数。但是,函数的每次调用都有自己的堆栈,它不能访问其范围之外的变量。
好的,在初始调用此函数时,smaller
和larger
将存储与小于和大于列表中第一个值的值相关联的列表。
smaller = [4, 5, 2, 5, 1, 5]
larger = [12, 14, 23]
这种分裂是通过列表理解完成的。我们遍历列表中除第一个值之外的每个值。如果要评估的当前值小于列表中的第一个值,则我们将其添加到smaller
列表中。然后,我们对larger
列表执行相同的操作。
现在返回功能是
return quickSort(smaller) + [6] + quickSort(larger)
让我们先做较小的一面。
quickSort([4, 5, 2, 5, 1, 5])
lst[0] = 4
smaller = [2, 1]
larger = [5, 5, 5]
return quickSort(smaller) + [4] + quickSort(larger)
所以这个过程一直持续到我们到达len(lst)<=1
并且递归向后解开的点。想象一下,每次调用函数时,树的一个新分支都会延伸。当函数最终返回时,值将传播回树中。最终结果是一个排序数组。
[1,2,4,5,5,5,6,12,14,23]
答案 1 :(得分:2)
我认为JahKnows's answer解释了你想要的几乎所有东西,并且比我更加简洁,但还有一些你似乎不清楚的要点。
首先,你担心内存泄漏&#34;。这里没有内存泄漏,因为Python使用垃圾收集器自动找出要删除的内容。 CPython实现(您可能正在使用的实现)使用显式内部引用计数:只要您对对象的最后引用(局部变量,列表元素,对象属性等)消失,对象就会消失被删除,其内存被释放。由于您熟悉C ++:这就像包裹在shared_ptr
中的每个值一样。其他实现使用更高级(更有效但不太确定)的收集器,但是,他们仍然无法删除您仍然引用的任何内容。
特别是,对quickSort
的每次递归调用都有自己的局部变量lst
,smaller
和larger
。你不能del
在任何地方,或为它们分配新值,所以它们不会消失,直到函数调用完成。函数调用无法完成,直到它完成的两个递归调用完成。
所以,你有O(log N)
递归&#34;帧&#34;在任何给定时间,每个都有O(N)
本地存储空间,因此使用的总空间为O(N log N)
。
你问在现场做事是否有优势,答案是:是的。它更复杂(并且还有其他一些缺点),但是如果你就地排序,并且你只是改变了传递下来并备份堆栈的单个列表的位,你就可以避免{ {1}}空间费用(加上一些O(N log N)
时间费用,但由于总时间为O(N)
...)。
如果不切换到原位,可以避免空间成本吗?好吧,除非您可以在重新递归时对结构列表进行结构化(这很难 - 在这种情况下,为什么不在原地进行?),您显然需要至少O(N long N)
个空间来进行任何复制分类。并且O(N)
并不比O(N log N)
差得多。但我们可以改进。
例如,考虑一下:
O(N)
但这显然更复杂,更容易出错。它甚至可能更慢。这值得么?好吧,如果你真的需要一个复制快速排序,并且正在使用的额外内存正在推动32位崩溃的边缘或驱使你进入64位交换地狱,那么是的。否则,可能不会。