快速排序定义:
快速排序(英语:Quicksort),又称分区交换排序(partition-exchange sort),简称快排,一种排序算法,最早由东尼·霍尔提出。在平均状况下,排序{\displaystyle n}n个项目要{\displaystyle \ O(n\log n)}{\displaystyle \ O(n\log n)}(大O符号)次比较。在最坏状况下则需要{\displaystyle O(n^{2})}{\displaystyle O(n^{2})}次比较,但这种状况并不常见。事实上,快速排序{\displaystyle \Theta (n\log n)}{\displaystyle \Theta (n\log n)}通常明显比其他演算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地达成。
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为较小和较大的2个子序列,然后递归地排序两个子序列。
步骤为:
- 挑选基准值:从数列中挑出一个元素,称为「基准」(pivot),
- 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成,
- 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。
递归到最底部的判断条件是数列的大小是零或一,此时该数列显然已经有序。选取基准值有数种具体方法,此选取方法对排序的时间性能有决定性影响。
来自 维基 快速排序~
这里进行一下对其的实现:
先说实现思路,在进行上面的步骤2 分割 操作的时候,这个基准的位置是会发生改变的,除非这个基准恰好满足左边的全部小于基准值,右边的全部大于基准值。
然后是怎么实现这个分割呢?在找到一个基准值之后。 比如这里是一个数组,找到某个数为基准值,那么,如何让左边的数全部小于基准值,且右边的全部大于基准值呢?
- 这里采用的方式是移位与填充的处理方式。比如找到左边的某个数是大于基准值的,那么得进行一个移位的操作。
考虑左右两边的情形:
- [基准值左边的移动] 比如现在有一个数组是
[1, 7, 0, 3, 4,5, 2]
, 假设,这里把基准设置为3
这个值,那么 左边的7
是需要放到3
的后面的,右边的先不考虑;那么移位之后,数组应该变成[1, 0, 3, 7, 4, 5, 2]
了。也就是说,这里是先把0,3
向左移动,腾出3
原来的位置,然后把7
插入到3
原来的位置上。(这个操作跟插入排序类似)这样一来呢,这个基准值3
就左移了一位; - [基准值右边的移动] 比如现在有一个数组是
[1, 7, 4, 3, 6, 2, 9]
,假设,基准值还是这个3
,那么右边的这个2
是需要移动到基准值前面去的。这时候呢,就把3,6
右移一位,然后把2
放到原来的3
的位置上去,这样之后,数组变成了[1, 7, 4, 2, 3, 6, 9]
; 同时这个基准值3
的位置就已经发生变化了,被右移了一位。 - 根据上面这两个方向的移动,可以总结出规律;这样无论基准左右两边有多少个不满足条件的元素,都会被移动到相反的地方去。
- 根据上面3条,然后就是分别对左边和右边进行递归调用,最终一定是得到一个有序的序列。
代码如下:
public static void quickSort(int[] array, int left, int right) {
// 2, 4, 6, 8, 7, 3, 5, 0, 9
if (SHOW_DETAIL) {
System.out.println("params: array = " + Arrays.toString(array) + ", left = " + left + ", right = " + right);
}
int pm = (left + right) / 2;
int pivot = array[pm];
// System.out.printf("pm=%d, pivot=%d\n", pm, pivot);
int val = pivot;
int counts;
// 处理左边,使左边的全部元素都小于 pivot @start
// 找到左边 大于 pivot的值, 然后,移动 (arr[t+1],arr[pm]) -> (arr[t],arr[pm-1]) ,并且把 arr[t] -> arr[pm]
counts = pm - left;
for (int count = 0; count < counts; ++count) {
boolean needMove = false;
for (int i = left; i < pm; ++i) {
if (needMove) {
array[i] = array[i + 1];
} else {
if (array[i] > pivot) {
// System.out.println("left=== " + array[i] + ", " + pivot);
val = array[i];
array[i] = array[i + 1];
needMove = true;
}
}
}
if (needMove) {
array[pm] = val;
pm--;
}
// System.out.printf("==left move: %s,pm=%d\n", Arrays.toString(array), pm);
}
// // 处理左边,使左边的全部元素都小于 pivot @end
// 处理右边,使右边的全部元素都大于 pivot @start
// 处理方式: 找个每个小于 pivot 的值,然后向右移动 (arr[pm],arr[t-1]) -> (arr[pm+1],arr[t])
counts = right - pm;
// System.out.printf("right--:pm=%d,right=%d,counts=%d, %s\n", pm, right, counts, Arrays.toString(array));
for (int count = 0; count < counts; ++count) {
boolean needMove = false;
for (int i = right; i > pm; --i) {
if (needMove) {
array[i] = array[i - 1];
} else {
if (array[i] < pivot) {
// System.out.println("===--" + array[i] + ", " + pivot);
val = array[i];
needMove = true;
array[i] = array[i - 1];
}
}
}
if (needMove) {
array[pm] = val;
pm++;
}
// System.out.println("right==move: " + Arrays.toString(array));
}
// 处理右边,使右边的全部元素都大于 pivot @end
if (SHOW_DETAIL) {
System.out.printf("each loop: %s, l=%d,r=%d,pm=%d\n", Arrays.toString(array), left, right, pm);
}
// then recursion it
if (left < pm) {
// recursion left side
quickSort(array, left, pm - 1);
}
if (pm + 1 < right) {
// recursion right side
quickSort(array, pm + 1, right);
}
}
注意看上面的代码,会发现这个递归其实没有终止条件。但是实际上不会出现栈溢出的问题。因为每次递归之前会判断输入的参数
left, right
。
调用如下:
int[] array= {2, -1, 3, -4, 5, -6, 7, -8, 9, 0};
quickSort(array, 0, array.length - 1);