基本思想:
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
快速排序的示例:
(a)一趟排序的过程:
(b)排序的全过程
算法的实现:
递归实现:
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] data = { 5, 1, 9, 3, 7, 4, 8, 6, 2 };
System.out.println("排序前:" + Arrays.toString(data));
quickSort(data, 0, data.length - 1);
System.out.println("排序后:" + Arrays.toString(data));
}
private static void quickSort(int[] data, int low, int high) {
int base;
if (low < high) {
base = partition(data, low, high);
quickSort(data, low, base - 1);
quickSort(data, base + 1, high);
}
}
private static int partition(int[] data, int i, int j) {
int base = data[i];// 选定第一个数为基准 (可以优化为三数取中,即取左右中三个数的中间数)
while (i < j) {
while (i < j && data[j] > base)
j--;
if (i < j) // 如果不加此比较,swap两个相同位置的记录,结果是0
swap(data, i, j); // 将比枢轴记录小的交换到低端
while (i < j && data[i] < base)
i++;
if (i < j)
swap(data, i, j); // 将比枢轴记录大的交换到高端
}
return i;
}
private static void swap(int[] a, int i, int j) {
a[i] = a[i] + a[j];
a[j] = a[i] - a[j];
a[i] = a[i] - a[j];
}
// 优化算法 优化递归操作及小数组时的排序方案
private static void quickSort1(int[] data, int low, int high) {
int base;
int max_length_insert_sort = 9;// 当high-low大于常数时用快速排序
if ((high - low) > max_length_insert_sort) {
while (low < high) {
base = partition1(data, low, high);
quickSort1(data, low, base - 1);//对低子表递归排序
low = base + 1;//尾递归
}
} else
new StraightInsertionSort().insertionSort2(data);// 否则用插入排序
}
// 优化不必要的交换
private static int partition1(int[] data, int i, int j) {
int base = data[i];// 选定第一个数为基准
int temp = base; // 备份枢轴
while (i < j) {
while (i < j && data[j] > base)
j--;
data[i] = data[j];// 采用替换而不是交换的方式进行操作
while (i < j && data[i] < base)
i++;
data[j] = data[i];// 采用替换而不是交换的方式进行操作
}
data[i] = temp;// 将枢轴值替换回来
return i;
}
// 三数取中 :即取三个关键字先进行排序,将中间数作为枢轴,一般取左端、右端、中间三个数。
public int getMiddle(int[] data, int i, int j) {
int m = i + (j - i) / 2;
if (data[i] < data[j])
swap(data, i, j);
if (data[m] > data[j])
swap(data, m, j);
if (data[m] > data[i])
swap(data, m, i);
return data[i];
}
}
通常我们在进行快速排序时,关键数据(即基准元素)一般选取序列的第一个元素,但在序列大部分有序时,这会致使最坏时间复杂度O(n^2)
的出现,为了避免这种情况的发生,我们可以选择以下几种方法对其进行优化:
- 三数取中;
- 优化递归操作;
- 使用并行或多线程处理子序列;
- 随机选择关键数据进行快排,可以使用
rand()
方法; - 当待排序序列的长度分割到一定大小后,使用插入排序;
- 在一次分割结束后,可以把与 Key 相等的元素聚在一起,继续下次分割时,不用再对与 key 相等元素分割。
算法分析:
快速排序的时间复杂度与关键字初始序列有关。
最坏时间复杂度:O(n^2):
以第一个数或最后一个数为基准时,当初始序列整体或局部有序时,快速排序的性能会下降。若整体有序,此时,每次划分只能分出一个元素,具有最坏时间复杂度,快速排序将退化成冒泡排序。
最好时间复杂度:O(n*以2为底n的对数) (平均)
每次选取的基准关键字都是待排序列的中间值,也就是说每次划分可以将序列划分为长度相等的两个序列。快速排序的递归过程可以用一棵二叉树来表示,递归树的高度是2为底的对数,每层需要比较的次数是n/2,所以最好时间复杂度是O(n*以2为底n的对数),因为很多时候输入序列都是乱序的,所以最好时间复杂度也是平均时间复杂度。
不稳定!