为什么我们更喜欢对文件的较小分区进行排序,并在为快速排序(非递归实现)分区后将更大的分区推入堆栈?这样做可以减少随机文件的快速排序O(log n)的空间复杂性。有人可以详细说明吗?
答案 0 :(得分:11)
如您所知,在每个递归步骤中,您对数组进行分区。将较大的部件推到堆叠上,继续在较小的部件上工作。
因为你携带的那个是较小的一个,它至多是你之前工作的一半。因此,对于我们推动堆叠的每个范围,我们将我们正在使用的范围的大小减半。
这意味着在我们处理大小为1的范围(因此被排序)之前,我们不能将超过log n
范围推送到堆栈上。这限制了我们完成第一次下降所需的堆栈数量。
当我们开始处理“大部分”时,每个“大部分”B(k)都比同时产生的“小部分”S(k)大,所以我们可能需要更多的堆栈来处理B( k)比我们需要处理S(k)。但是B(k)仍然小于之前的“小部分”,S(k-1),一旦我们处理B(k),我们已经把它从堆栈中取回,因此,它比我们处理S(k)时小一个项目,并且与我们处理S(k-1)时的大小相同。所以我们仍然有自己的约束力。
假设我们以相反的方式做到了 - 推动小部件并继续使用大部件。然后在病态恶劣的情况下,我们每次都会在堆栈上推送一个1
范围的大小,并继续使用比之前大小小2的大小。因此,我们在堆栈中需要n / 2
个插槽。
答案 1 :(得分:3)
考虑最糟糕的情况,即以您的分区为1:n的方式进行分区。如果您首先排序小子文件而不是只需要使用O(1)空间,则推送大型子文件然后将其弹回(然后再次推送大子文件)。但是,如果您首先对大型子文件进行排序,而不是需要O(N)空间,因为您继续在堆栈中推送1个元素数组。
以下是来自ROBERT SEDGEWICK的算法的引用(他是那个写论文的人):
对于Quicksort,结束了递归删除和策略的组合 首先处理两个子文件中较小的子文件 确保堆栈只需要包含大约lg N个条目的空间, 因为在顶部之后的堆栈上的每个条目必须代表一个 子文件小于前一个条目大小的一半。
答案 2 :(得分:1)
好吧,我是对的,你的意思是如果我们使Quicksort算法非递归,你必须使用一个堆栈,你把堆栈放在堆栈上?
如果是这样的话:算法必须为每个使用内存的变量分配。因此,如果你并行运行它的两个实例,它们将分配一个算法内存空间的双倍数量......
现在,在递归版本中,您启动算法的新实例(需要分配内存)但是调用递归的实例,不会结束,因此需要分配的内存! - >事实上,我们已经开始让10个递归实例,需要10 * X内存,其中X是一个实例所需的内存。
现在,我们使用非递归算法。你必须只分配所需的内存ONCE。实际上,辅助变量只使用一个实例的空间。要完成算法的功能,我们必须保存已经创建的分区(或者我们尚未完成的分区)。事实上,我们把它放在一个堆栈上并取下分区,直到我们进行最后一次“递归”步骤。所以,想象一下:你给这个算法一个数组。递归算法需要为每个实例分配整个数组和一些辅助变量(同样:如果递归深度为10,我们需要10 * X内存,其中数组需要很多)。 非递归的只需要一次分配数组,辅助变量但它需要一个堆栈。但是,最后你不会在堆栈上放置这么多的部分,递归算法需要更少的内存,因为我们不需要每次/实例再次分配数组。
我希望,我已经对它进行了描述,以便你能够理解它,但我的英语并不是那么好。 :)