快速排序的平均时间复杂度是 O(nlogn),但最坏情况下会变成 O(n2)。核心思想是分治法,也就是说选定一个标准,将数组的值划分为大于标准,和小于等于标准两组,直到不能继续划分为止。所以这里的关键也在于这个标准值要怎么取。通常的取法是取数组第一位,或者随机一个数在与第一位交换。
快速排序的优势在于,数组处于乱序状态下会有一个较好的表现。
思路
实现的思路主要在于怎么划分,这里就以数组第一位为标准值。划分操作可分为两种,
- 单边循环
这个比较符合我们一开始的思路,遍历数组,然后通过对比,我们最终想要的效果是小的在左,大的在右,基值(标准值)在中间。
具体步骤是这样,
- 标记当前 <= 基值的边界,然后从基值(基值这里取数组第一位,即下标为 0 的那位)后一位开始遍历。
- 当遍历发现 <= 基值时,这个标记就 +1 表示边界扩大了,并且在当前遍历的下标与标记不相同时,交换元素。
- 当遍历发现 > 基值时,就继续遍历。
- 遍历结束后,<= 基值的都在前面,> 基值的都在后面,此时再交换一下基值与标记的元素,就是我们想要的小的在左,大的在右,基值在他们中间。
- 这样完成了一趟分治,接着利用递归,分别对基值左边和基值右边的数组重复上面四步,直到数组没办法再划分为止。
- 双边循环
意思是分别从数组的两头开始与基值(标准值)比较,这样看来会比单边循环效率高些。
具体步骤是这样,
- 确定好数组的左右两头,基值依然选择数组的第一位。
- 在左右两头标记不想碰(left < right 或者 left != right)的前提下,循环操作。
- 具体操作是,先从右边开始递减遍历,如发现 <= 基值就跳出循环。
- 基于步骤 3 ,在从左边出发开始递增遍历,如发现 > 基值就跳出循环。
- 基于步骤 3,4 此时如果左边依然 < 右边,则交换左右的元素值。然后继续步骤 2 的循环。
- 这样一来,左右标记最终会重合,这样也就完成了一次分治。
- 同样,此时将基值与左或者右标记进行元素交换。
- 剩下的仍然是对基值左右两边的数组进行上述的重复过程,直到数组没办法再划分为止。
上面两种划分实现都用到了递归,但其实递归可以转换成栈的操作,因此栈的实现思路是这样的,
联想方法间的调用其实就是入栈的操作,因为在分治的过程中需要知道数组的左右边界,因此可以将左右边界的信息作为栈元素进行操作。所以这个栈的转换我觉得更侧重一个思想,我一开始想的栈的转换是如何利用栈来实现分治操作,一直想不通,后来又看了书才明白是这么转换。
代码
代码实现上分别做了单边循环,双边循环以及栈的转换。
github 地址:Sorting 类下面的 quickSort 方法