快速排序(Java代码实现)
1. 思想
快速排序是一种分治(Divide and conquer)递归算法。快速排序使用分治法把一个序列分割成两个子序列,其中一部分序列均比另一部分序列小,之后再递归地分别对这两部分序列继续进行排序。
2. 算法分析
快速排序是实践中的一种快速的排序算法,在对C++和Java的基本类型的排序中特别有效。它的平均时间复杂度是O(n log n),虽然它最坏情况下的时间复杂度为 O(n²),但经过一些优化可以让这种情况很难出现。
排序算法 | 平均时间复杂度 | 最好时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 排序方式 | 稳定性 |
---|---|---|---|---|---|---|
快速排序 | O(n log n) | O(n log n) | O(n2) | O(log n) | 内部排序 | 不稳定 |
3. 实现步骤
- 从序列中选取一个元素作为 ”基准/枢轴“(pivot);
- 把序列中比基准值小的元素移动到基准左边,比基准值大的元素移动到基准的右边(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个操作称为分区/分割(partition);
- 递归地(recursive)把小于基准值的元素子序列和大于基准值的元素的子序列进行排序。
数据结构与算法分析(Java语言描述)书中的原话:
例子:
4. 具体步骤实现
通过前面的步骤分析我们知道第一步先选取元素作为基准,第二步再把大于基准的元素移到基准的左边,小于基准的元素移到基准的右边,那么我们在实际当中,在代码中应该怎么去实现这两步?
4.1 选取基准
怎么样选取基准?
-
一种错误的做法:选择固定位置作为基准
在序列中选取第一个元素作为基准。
Java代码实现:
// 固定位置:选取数组的第一个元素作为基准
public static int selectPivot(int array[], int low) {
return array[low];
}
-
一种安全的做法:随机选取基准
在序列中随机选取一个元素作为基准。
Java代码实现:
//随机选取基准:选取序列中的随机一个元素作为基准
public static int selectPivotRandom(int array[], int low, int high) {
Random random = new Random();
// nextInt(n)生成的随机数的范围不包括n,所以加1。
int rd = random.nextInt(high - low + 1) + low;
swap(array, high, rd);
return array[high];
}
-
一种较优的做法:三数中值法
选取序列最左端、最右端和中间的元素的中值作为基准。
例子:
假设待排序列为:8,1, 4, 9, 6, 3, 5, 2, 7, 0,
它最最左端的元素为:8,最右端的元素为:0,中心位置 (left + right)/2 上的元素为:6。
这三个位置上的元素的中值为:6,所以选取6作为基准。
Java代码实现:
// 三数中值:选取序列中最左端,最右端,中间的元素的中值作为基准
public static int medianOfThree(int[] array, int low, int high) {
//序列中间的元素的下标
int mid = low + ((high - low) >> 1);
if (array[mid] > array[high]) {
swap(array, mid, high);
}
if (array[low] > array[high]) {
swap(array, low, high);
}
if (array[mid] > array[low]) {
swap(array, mid, low);
}
//此时,array[mid] <= array[low] <= array[high]
//low的位置上保存这三个位置中间的值
//分割时可以直接使用low位置的元素作为枢轴,而不用改变分割函数
return array[low];
}
4.2 分区操作和递归
怎么将把序列中比基准值小的元素移动到基准左边,比基准值大的元素移动到基准的右边?
双向扫描:
从右往左扫描,第一个比基准值小的元素与基准值互换;
从右往左扫描,第一个比基准值大的元素与基准值互换。
第一个比基准值小的元素的位置开始从右往左扫描,扫描到第二个比基准值小的元素与基准值互换。
第一个比基准值小的元素的位置开始从右往左扫描,扫描到第二个比基准值大的元素与基准值互换。
······
第n个比基准值小的元素的位置开始从右往左扫描,扫描到第(n+1)个比基准值小的元素与基准值互换。
第n个比基准值小的元素的位置开始从右往左扫描,扫描到第(n+1)比基准值大的元素与基准值互换。
······
直到指针low = high,完成第一次分割操作。
最后再递归的重复以上步骤完成排序。
例子:
Java代码实现:
// 快速排序
public static void quickSort(int array[], int low, int high) {
if (low < high) {
//选取准基
int pivot = medianOfThree(array, low, high);
int i = low;
int j = high;
//分区
while (i < j) {
//从后往前(当元素大于基准时往前移动,直到元素小于基准)
while ((i < j) && (array[j] > pivot)) {
j--;
}
swap(array, i, j);
//从前往后(当元素小于基准时往前移,直到元素大于基准)
while ((i < j) && (array[i] < pivot)) {
i++;
}
swap(array, i, j);
}
//递归
if (i - 1 > low) {
quickSort(array, low, j - 1);
}
if (j + 1 < high) {
quickSort(array, j + 1, high);
}
}
}
5.完整代码
public class QuickSort {
public static void main(String[] args) {
int[] array = {5, 3, 7, 6, 4, 1, 0, 2, 9, 10, 8};
int low = 0;
int high = array.length - 1;
quickSort(array, low, high);
System.out.println(Arrays.toString(array));
}
// 快速排序
public static void quickSort(int array[], int low, int high) {
if (low < high) {
//选取准基
int pivot = medianOfThree(array, low, high);
int i = low;
int j = high;
//分区
while (i < j) {
//从后往前(当元素大于基准时往前移动,直到元素小于基准)
while ((i < j) && (array[j] > pivot)) {
j--;
}
swap(array, i, j);
//从前往后(当元素小于基准时往前移,直到元素大于基准)
while ((i < j) && (array[i] < pivot)) {
i++;
}
swap(array, i, j);
}
//递归
if (i - 1 > low) {
quickSort(array, low, j - 1);
}
if (j + 1 < high) {
quickSort(array, j + 1, high);
}
}
}
// 选择固定位置:选取数组的第一个元素作为基准。
public static int selectPivot(int array[], int low) {
return array[low];
}
// 随机选取基准:选取序列中的随机一个元素作为基准。
public static int selectPivotRandom(int array[], int low, int high) {
Random random = new Random();
// nextInt(n)生成的随机数的范围不包括n,所以加1。
int rd = random.nextInt(high - low + 1) + low;
swap(array, high, rd);
return array[high];
}
// 三数中值:选取序列中最左端,最右端,中间的元素的中值作为基准。
public static int medianOfThree(int[] array, int low, int high) {
//序列中间的元素的下标
int mid = low + ((high - low) >> 1);
if (array[mid] > array[high]) {
swap(array, mid, high);
}
if (array[low] > array[high]) {
swap(array, low, high);
}
if (array[mid] > array[low]) {
swap(array, mid, low);
}
return array[low];
}
// swap
public static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}