[toc]
快速排序
前言
建议先看排序综述,传送门:数据结构与算法系列之一:八大排序综述。
简介
快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),一种排序算法,最早由东尼·霍尔提出。在平均状况下,排序 ${\displaystyle n}$ 个项目要 ${\displaystyle O(n\log n)}$ (大O符号)次比较。在最坏状况下则需要 ${\displaystyle O(n^{2})}$ 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 ${\displaystyle O(n\log n)}$ 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
步骤
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
步骤为:
- 从数列中挑出一个元素,称为”基准”(pivot).
- 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
- 递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
wikipedia的大数据规模演示:
wordzzzz的小数据规模演示:
代码
先给出公用接口,之后的三个递归实现和一个迭代实现在代码中都有详细的说明,我就不再在此赘述。
公用接口
1 | /* |
递归法
1 | /* |
迭代法
1 | /* |
算法复杂度
- 数据结构 不定
- 最坏时间复杂度 ${\displaystyle O(n^{2})}$
- 最优时间复杂度 ${\displaystyle O(n\log n)}$
- 平均时间复杂度 ${\displaystyle O(n\log n)}$
- 空间复杂度 根据实现的方式不同而不同
分析
快速排序是二叉查找树(二叉查找树)的一个空间最优化版本。不是循序地把数据项插入到一个明确的树中,而是由快速排序组织这些数据项到一个由递归调用所隐含的树中。这两个算法完全地产生相同的比较次数,但是顺序不同。对于排序算法的稳定性指标,原地分区版本的快速排序算法是不稳定的。其他变种是可以通过牺牲性能和空间来维护稳定性的。
快速排序的最直接竞争者是堆排序(Heapsort)。堆排序通常比快速排序稍微慢,但是最坏情况的运行时间总是 ${\displaystyle O(n\log n)}$ 。快速排序是经常比较快,除了introsort变化版本外,仍然有最坏情况性能的机会。如果事先知道堆排序将会是需要使用的,那么直接地使用堆排序比等待introsort再切换到它还要快。堆排序也拥有重要的特点,仅使用固定额外的空间(堆排序是原地排序),而即使是最佳的快速排序变化版本也需要 ${\displaystyle O(\log n)}$ 的空间。然而,堆排序需要有效率的随机存取才能变成可行。
快速排序也与归并排序(Mergesort)竞争,这是另外一种递归排序算法,但有坏情况 ${\displaystyle O(n\log n)}$ 运行时间的优势。不像快速排序或堆排序,归并排序是一个稳定排序,且可以轻易地被采用在链表(linked list)和存储在慢速访问媒体上像是磁盘存储或网络连接存储的非常巨大数列。尽管快速排序可以被重新改写使用在链串列上,但是它通常会因为无法随机存取而导致差的基准选择。归并排序的主要缺点,是在最佳情况下需要 ${\displaystyle \Omega (n)}$ 额外的空间。
局限性
快排的优化、归并排序的优化一向是面试的考察重点,至于算法的优化,重点还是要知道现有算法的不足之处。
- 当序列长度很小时,快排效率低,研究表明长度在5~25的数组,快排表现不如插入排序。
- 当pivot选择不当是,会导致树的不平衡,这样导致快排的时间复杂度为${\displaystyle O(n^{2})}$。
- 当数组中有大量重复的元素,快排效率将非常之低。
优化
针对上面提出的快排的局限性,我们依次做出优化策略:
- 当当前序列长度小于特定值时,直接采用插入排序,或者不做处理,等到快排都执行完毕后(大致有序)在执行一次插入排序。
- 针对pivot的选择,不再选取固定值,而是采用其他选取策略,如随机、三值取中等。
- 如果数组中重复元素多,就采用三路划分算法:以某个数为基准将一个数组分成三部分:第一部分表示小于该pivot,第二部分等于pivot,第三部分大于pivot,要得到三部分得区间范围。
下面的代码是对上述改进算法的实现:
1 | /* |
系列教程持续发布中,欢迎订阅、关注、收藏、评论、点赞哦~~( ̄▽ ̄~)~
完的汪(∪。∪)。。。zzz