Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Algorithm 快速排序-应首先对哪个子部分进行排序?_Algorithm_Sorting_Quicksort - Fatal编程技术网

Algorithm 快速排序-应首先对哪个子部分进行排序?

Algorithm 快速排序-应首先对哪个子部分进行排序?,algorithm,sorting,quicksort,Algorithm,Sorting,Quicksort,我正在阅读一些关于两个递归快速排序调用顺序的文章: 。。。首先调用较小的子问题是很重要的,这与尾部递归一起确保堆栈深度为logn 我完全不知道这意味着什么,为什么我要先在较小的子数组上调用Quicksort?理想情况下,该列表被划分为两个大小大致相似的子列表。首先处理哪个子列表并不重要 但是如果在一个糟糕的日子里,列表以最不平衡的方式进行划分,一个包含两个或三个项目的子列表,可能是四个,并且一个子列表几乎和原始列表一样长。这可能是由于分区值的错误选择或恶意设计的数据造成的。想象一下,如果您先处理

我正在阅读一些关于两个递归快速排序调用顺序的文章:

。。。首先调用较小的子问题是很重要的,这与尾部递归一起确保堆栈深度为logn


我完全不知道这意味着什么,为什么我要先在较小的子数组上调用Quicksort?

理想情况下,该列表被划分为两个大小大致相似的子列表。首先处理哪个子列表并不重要

但是如果在一个糟糕的日子里,列表以最不平衡的方式进行划分,一个包含两个或三个项目的子列表,可能是四个,并且一个子列表几乎和原始列表一样长。这可能是由于分区值的错误选择或恶意设计的数据造成的。想象一下,如果您先处理较大的子列表,会发生什么。快速排序的第一个调用是在堆栈框架中保留短列表的指针/索引,同时递归调用长列表的快速排序。这太糟糕了,分成一个很短的列表和一个很长的列表,我们先做较长的子列表,重复

最终,在最糟糕的日子里,使用最糟糕的数据,我们将以与原始列表长度成比例的数量构建堆栈帧。这是quicksort最糟糕的行为,递归调用的O(n)深度。(注意,我们谈论的是快速排序的递归深度,而不是性能。)


首先执行较短的子列表可以相当快地消除它。我们仍然按照原始列表长度的比例处理大量的小列表,但现在每个列表都由一个或两个浅层递归调用处理。我们仍然进行O(n)调用(性能),但每个调用都是深度O(1)。

有些语言有尾部递归。这意味着,如果您编写f(x){…………g(x)},那么对g(x)的最终调用根本不是通过函数调用实现的,而是通过跳转实现的,因此最终调用不会使用任何堆栈空间

快速排序将要排序的数据拆分为两个部分。如果总是先处理较短的部分,那么每个占用堆栈空间的调用都有一部分要排序的数据,其大小最多为调用它的递归调用的一半。因此,如果从10个元素开始排序,堆栈最深处将有一个对这10个元素进行排序的调用,然后是最多5个元素的调用排序,然后是最多2个元素的调用排序,然后是最多1个元素的调用排序,然后是10个元素的调用排序,堆栈不能更深-堆栈大小受数据大小日志的限制


如果您不担心这一点,那么堆栈最终可能会包含一个调用,对10个元素进行排序,然后调用对9个元素进行排序,然后调用对8个元素进行排序,依此类推,这样堆栈的深度与要排序的元素数相同。但是如果先对短段进行排序,尾递归就不会发生这种情况,因为尽管可以将10个元素分成1个元素和9个元素,但调用排序9个元素是最后一个完成的,并作为跳转实现,它不使用任何堆栈空间-它重用调用方以前使用的堆栈空间,无论如何,它都将返回。

令人惊讶的是,即使quicksort没有遇到严重不平衡的分区,甚至在实际使用introsort时,这一点也非常重要

当被排序的容器中的值非常大时,问题就出现了(在C++中)。我这样说并不是说它们指的是非常大的物体,而是它们本身非常大。在这种情况下,一些(可能很多)编译器也会使递归堆栈帧变得相当大,因为它至少需要一个临时值才能进行交换。Swap在分区内部调用,分区本身不是递归的,因此您可能认为快速排序递归驱动程序不需要monster堆栈框架;不幸的是,分区最终通常是内联的,因为它很好,很短,并且没有从其他任何地方调用

通常情况下,20和40堆栈帧之间的差异可以忽略不计,但是如果值的权重为8kb,那么20和40堆栈帧之间的差异可能意味着工作和堆栈溢出之间的差异,前提是堆栈的大小已减小,以允许多个线程

如果使用“始终递归到较小分区”算法,堆栈不能每超过log2n帧,其中N是向量中的元素数。此外,N不能超过可用内存量除以元素大小。因此,在32位机器上,向量中只能有219个8kb的元素,快速排序调用深度不能超过19


简言之,正确编写快速排序可以预测其堆栈使用情况(只要可以预测堆栈帧的大小)。不进行优化(保存一次比较!)很容易导致堆栈深度翻倍,即使在非病理性的情况下也是如此,而在病理性的情况下,情况会变得更糟。

将快速排序视为隐式二叉树。轴是根,左、右子树是您创建的分区

现在考虑对这棵树进行深度优先搜索。递归调用实际上对应于对上述隐式树进行深度优先搜索。还假设树总是有较小的子树作为左子树,因此建议实际上是对这棵树进行预排序

现在假设您使用一个堆栈实现预排序,您只推左边的子级(但将父级保留在堆栈上),当推右边的子级时(假设您保持一个状态,知道节点是否有左边的子级),您将替换堆栈的顶部,而不是推右边的子级(这对应于尾部递归部分)

最大堆栈深度是最大“左深度”:即,如果标记每条边,则