快速排序与冒泡排序相似,也属于交换排序,经过元素之间的比较和交换位置实现排序。不一样的地方在于,冒泡排序在每一次循环只把一个元素冒泡到数组的一端,而快速排序在每一轮挑选一个枢纽元,并让其余比它大的元素移动到数组的一边,比它小的元素移动到数组另外一边,从而把数组拆分红两个部分。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
最简单的方法就是将第一个元素做为枢纽元。可是这种策略不可取,假设输入的数组是预排序的,那么枢纽元必然会选到最大或最小的元素,这种状况下每一轮循环数组并无被分红两半,不能发挥分治法的优点。在这种极端状况下,快速排序须要进行 n 轮,时间复杂度退化为 O(n2) 。htm
一种很是安全的作法是随机选取枢纽元。说这个策略安全,是由于随机的枢纽元不可能在每一轮循环都产生劣质的分割(即选到了最大或最小值)。可是这个策略也有问题,一是即便是随机选取的枢纽元,也有必定几率会选中最大或最小值,影响分治效率;二是随机数的生成通常开销很大,影响总体算法效率。blog
枢纽元的最好选择是数组的中值(也叫作中位数,是第 N/2 个最大的数),可是中值难以求出,而且会明显下降快速排序的效率。通常的作法是使用左端、右端和中间三个元素的中值做为枢纽元。例如输入的数组以下:排序
[8, 1, 4, 9, 6, 3, 5, 2, 7, 0]
左端元素是8,右端元素是0,中间的元素是6,所以能够肯定枢纽元 v = 6
。显然,使用三数中值分割法消除了预排序输入致使的最坏情形。
选取了枢纽元以后,下一步就须要移动元素,将数组拆分红两个部分。有个简便的方法是直接开三个数组,分别是 smaller
, same
,larger
,而后循环遍历数组的每一个元素,将每一个元素与枢纽元对比,小于枢纽元的 push 进 smaller
数组,等于枢纽元的 push 进 same
数组,大于枢纽元的 push 进 larger
数组,这样就实现了拆分。可是这样作无疑会占用额外空间,实际的快排(例如 JDK 的 sort 方法)都是直接对原数组进行排序的,这样就要求直接在数组中交换元素,实现数组的分割。主要有两种方法。
首先选定枢纽元 pivot ,而且设置两个指针 left 和 right ,初始状态下 left 和 right 分别位于数组最左和最右侧。
接下来进行第1次循环,从 right 指针开始,让指针所指向的元素和 pivot 进行比较,若是大于或等于 pivot ,则指针向左移动;若是小于 pivot ,则 right 指针中止移动,切换到 left 指针。轮到 left 指针行动,让指针所指向的元素和 pivot 进行比较,若是小于或等于 pivot ,则指针向右移动;若是大于 pivot ,则 left 中止移动。
当两个指针都中止移动时,让 left 和 right 指针所指向的元素进行交换。
而后进行下一轮循环,以此类推。
《数据结构与算法分析(Java 语言描述)》
《漫画算法》
图解排序算法(五)之快速排序——三数取中法