Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/11.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 直观解释为什么快速排序是n log n?_Algorithm_Complexity Theory_Quicksort - Fatal编程技术网

Algorithm 直观解释为什么快速排序是n log n?

Algorithm 直观解释为什么快速排序是n log n?,algorithm,complexity-theory,quicksort,Algorithm,Complexity Theory,Quicksort,是否有人能给出一个“简明英语”的直观,但正式的解释是什么使快速排序n日志n?根据我的理解,它必须传递n个项目,并且它会记录n次……我不知道如何用文字表达为什么它会记录n次。每个分区操作需要O(n)个操作(在阵列上传递一次)。 平均而言,每个分区将数组分成两部分(总计为logn操作)。我们总共有O(n*logn)个操作 也就是说,在平均对数n个分区操作中,每个分区需要O(n)个操作。复杂性 快速排序首先将输入划分为两个块:它选择一个“pivot”值,并将输入划分为小于pivot值和大于pivot值

是否有人能给出一个“简明英语”的直观,但正式的解释是什么使快速排序n日志n?根据我的理解,它必须传递n个项目,并且它会记录n次……我不知道如何用文字表达为什么它会记录n次。

每个分区操作需要O(n)个操作(在阵列上传递一次)。 平均而言,每个分区将数组分成两部分(总计为logn操作)。我们总共有O(n*logn)个操作

也就是说,在平均对数n个分区操作中,每个分区需要O(n)个操作。

复杂性 快速排序首先将输入划分为两个块:它选择一个“pivot”值,并将输入划分为小于pivot值和大于pivot值的两个块(当然,任何等于轴心值的值都会进入其中一个或另一个,但对于基本描述来说,它们最终进入哪一个并不重要)

因为输入(根据定义)没有排序,所以要像那样对其进行分区,它必须查看输入中的每个项,因此这是一个O(N)操作。在第一次对输入进行分区后,它会递归地对每个“块”进行排序。每个递归调用都会查看它的每个输入,因此在两个调用之间,它最终会访问每个输入值(再次)。因此,在分区的第一个“级别”中,我们有一个调用会查看每个输入项。在第二个级别中,我们有两个分区步骤,但在这两个步骤之间,它们(再次)查看每个输入项。每个后续级别都有更多单独的分区步骤,但总的来说,每个级别的调用都会查看所有的输入项

它继续将输入分割成越来越小的部分,直到它达到分区大小的某个下限。每个分区中可能最小的是单个项

理想情况 在理想情况下,我们希望每个分区步骤将输入分成两半。“两半”可能并不完全相等,但如果我们选择好轴,它们应该非常接近。为了保持数学简单,让我们假设完美的分区,因此每次都得到精确的两半

在这种情况下,我们可以将其分成两半的次数将是输入数的基数2对数。例如,给定128个输入,我们得到的分区大小为64、32、16、8、4、2和1。这是7个分区级别(是的,log2(128)=7)

因此,我们有日志(N)分区“级别”,每个级别都必须访问所有N个输入。因此,日志(N)级别乘以每个级别的N个操作会给我们O(N logn)的总体复杂性

最坏情况 现在让我们回顾一下每个分区级别都将“中断”的假设输入精确地分成两半。取决于我们选择的分区元素有多好,我们可能无法得到精确相等的两半。那么可能发生的最坏情况是什么?最坏情况是一个枢轴,它实际上是输入中最小或最大的元素。在这种情况下,我们做一个O(N)分区级别,但我们没有得到大小相等的两个分区,而是得到了一个由一个元素组成的分区和一个由N-1个元素组成的分区。如果每一个分区级别都出现这种情况,那么很明显,在分区降到一个元素之前,我们就完成了O(N)个分区级别

这为快速排序提供了技术上正确的big-O复杂性(big-O正式表示复杂性的上限)。由于我们有O(N)个分区级别,并且每个级别都需要O(N)个步骤,因此我们最终得到了O(N*N)(即O(N2))复杂性

实际实施 实际上,真正的实现通常会在实际到达单个元素的分区之前停止分区。在典型情况下,当一个分区包含(比如)10个或更少的元素时,您将停止分区,并使用类似插入排序的方法(因为对于少量元素来说,它通常更快)

改进算法 最近还发明了对快速排序的其他修改(例如Introsort、PDQ Sort),它们可以防止O(N2)最坏的情况。Introsort通过跟踪当前分区的“级别”来实现这一点,并且当/如果它太深时,它将切换到堆排序,这比典型输入的快速排序慢,但保证O(N log N)任何输入的复杂性

PDQ排序为这一点增加了另一个转折点:由于堆排序速度较慢,它尽量避免切换到堆排序(如果可能的话),如果它看起来得到的数据透视值很差,它会在选择数据透视之前随机洗牌一些输入它不能产生足够好的支点值,它会切换到使用堆排序,而

嗯,它并不总是n(log n)。它是当选择的枢轴大致位于中间时的性能时间。在最坏的情况下,如果选择最小或最大的元素作为枢轴,则时间将为O(n ^ 2)。 要可视化“n log n”,可以假设pivot是最接近要排序的数组中所有元素的平均值的元素。 这将把数组分成两个长度大致相同的部分。 在这两种情况下,都应用快速排序过程


在每一步中,你都要将数组的长度减半,这样做的次数为logN(以2为基数),直到达到length=1,即一个1个元素的排序数组。

事实上,你需要找到所有n个元素(枢轴)的位置,但每个元素的最大比较次数是logN(第一个是N,第二个是轴N/2,第三个是轴N/4..假设轴是中间元素)

对数背后有一个关键的直觉:

在达到1之前,可以将数字n除以常数的次数为O(对数n)

换句话说,如果你看到一个运行时有一个O(logn)项,你很有可能会发现某些东西会以一个常数因子反复收缩

在快速排序中,什么是收缩常数