排序算法:快速排序

快速排序与冒泡排序相似,也属于交换排序,经过元素之间的比较和交换位置实现排序。不一样的地方在于,冒泡排序在每一次循环只把一个元素冒泡到数组的一端,而快速排序在每一轮挑选一个枢纽元,并让其余比它大的元素移动到数组的一边,比它小的元素移动到数组另外一边,从而把数组拆分红两个部分html

例如输入以下的数组,枢纽元选取为6

[8, 1, 4, 9, 0, 3, 5, 2, 7, 6]算法

分割以后结果以下数组

[2, 1, 4, 5, 0, 3, 6, 8, 7, 9]安全

与归并排序同样,快速排序也是一种分治的递归算法。在分治法的思想下,原数组在每一轮都被拆分红两个部分,每一部分在下一轮又被拆分红两部分,直到不可再分为止。数据结构

快速排序的平均时间复杂度是 O(n logn) , 最坏情形的时间复杂度为 O(n2) ,即每一轮循环枢纽元都选到最大或最小的元素,但通过稍许努力可使这种状况极难出现。快速排序有两个核心的问题,枢纽元的选择,以及元素的交换指针

选取枢纽元

枢纽元(pivot),也叫基准元素。在分治过程当中,以枢纽元为中心,把其余元素移动到它的左右两边。选取枢纽元主要有三种方法。code

1)选取第一个元素

最简单的方法就是将第一个元素做为枢纽元。可是这种策略不可取,假设输入的数组是预排序的,那么枢纽元必然会选到最大或最小的元素,这种状况下每一轮循环数组并无被分红两半,不能发挥分治法的优点。在这种极端状况下,快速排序须要进行 n 轮,时间复杂度退化为 O(n2) 。htm

2)随机选取元素

一种很是安全的作法是随机选取枢纽元。说这个策略安全,是由于随机的枢纽元不可能在每一轮循环都产生劣质的分割(即选到了最大或最小值)。可是这个策略也有问题,一是即便是随机选取的枢纽元,也有必定几率会选中最大或最小值,影响分治效率;二是随机数的生成通常开销很大,影响总体算法效率。blog

3)三数中值分割法(Median-of-Three Partitioning)

枢纽元的最好选择是数组的中值(也叫作中位数,是第 N/2 个最大的数),可是中值难以求出,而且会明显下降快速排序的效率。通常的作法是使用左端、右端和中间三个元素的中值做为枢纽元。例如输入的数组以下:排序

[8, 1, 4, 9, 6, 3, 5, 2, 7, 0]

左端元素是8,右端元素是0,中间的元素是6,所以能够肯定枢纽元 v = 6 。显然,使用三数中值分割法消除了预排序输入致使的最坏情形。

分割策略

选取了枢纽元以后,下一步就须要移动元素,将数组拆分红两个部分。有个简便的方法是直接开三个数组,分别是 smallersamelarger ,而后循环遍历数组的每一个元素,将每一个元素与枢纽元对比,小于枢纽元的 push 进 smaller 数组,等于枢纽元的 push 进 same 数组,大于枢纽元的 push 进 larger 数组,这样就实现了拆分。可是这样作无疑会占用额外空间,实际的快排(例如 JDK 的 sort 方法)都是直接对原数组进行排序的,这样就要求直接在数组中交换元素,实现数组的分割。主要有两种方法。

1)双边循环法

首先选定枢纽元 pivot ,而且设置两个指针 left 和 right ,初始状态下 left 和 right 分别位于数组最左和最右侧。

接下来进行第1次循环,从 right 指针开始,让指针所指向的元素和 pivot 进行比较,若是大于或等于 pivot ,则指针向移动;若是小于 pivot ,则 right 指针中止移动,切换到 left 指针。轮到 left 指针行动,让指针所指向的元素和 pivot 进行比较,若是小于或等于 pivot ,则指针向移动;若是大于 pivot ,则 left 中止移动。

当两个指针都中止移动时,让 left 和 right 指针所指向的元素进行交换

而后进行下一轮循环,以此类推。

2)单边循环法

参考

《数据结构与算法分析(Java 语言描述)》
《漫画算法》
图解排序算法(五)之快速排序——三数取中法

相关文章
相关标签/搜索