快速排序
基本原理
快速排序的核心思想是:
- 选择一个"切分元素"(pivot)
- 将数组分为三部分:小于切分元素的部分、切分元素本身、大于切分元素的部分
- 递归地对小于和大于切分元素的两部分进行排序
当递归到足够小的子数组时,排序完成。不同于归并排序在合并两个有序子数组时需要额外工作,快速排序的优势在于子数组排序完成后,整个数组自然就是有序的。
切分(Partition)过程
切分是快速排序最关键的步骤,其目标是:
- 选定一个切分元素v(通常选第一个元素)
- 重新排列数组,使所有小于v的元素在v的左侧,所有大于v的元素在v的右侧
- 返回切分元素的最终位置
基本实现思路:
- 从左向右扫描,找到大于等于切分元素的项
- 从右向左扫描,找到小于等于切分元素的项
- 交换这两项
- 重复上述过程直到左右指针相遇
基本实现代码
public class Quick {
public static void sort(Comparable[] a) {
StdRandom.shuffle(a); // 随机打乱数组,避免最坏情况
sort(a, 0, a.length - 1);
}
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int j = partition(a, lo, hi); // 切分
sort(a, lo, j-1); // 排序左半部分
sort(a, j+1, hi); // 排序右半部分
}
// 切分方法
private static int partition(Comparable[] a, int lo, int hi) {
int i = lo, j = hi + 1; // 左右扫描指针
Comparable v = a[lo]; // 切分元素
while (true) {
// 扫描左边,找到大于等于v的元素
while (less(a[++i], v))
if (i == hi) break;
// 扫描右边,找到小于等于v的元素
while (less(v, a[--j]))
if (j == lo) break;
// 指针相遇时退出循环
if (i >= j) break;
// 交换元素
exch(a, i, j);
}
// 将切分元素放入正确位置
exch(a, lo, j);
return j; // 返回切分元素的索引
}
// 辅助方法:比较两个元素
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
// 辅助方法:交换两个元素
private static void exch(Comparable[] a, int i, int j) {
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
}
性能特点
-
时间复杂度:
- 平均情况:O(n log n)
- 最坏情况:O(n²)(当数组已排序或逆序排序时)
- 最好情况:O(n log n)
-
空间复杂度:
-
O(log n)(递归调用栈的空间)
-
优点:
-
原地排序(不需要额外的大数组)
- 平均性能优秀
- 内循环简短,比较次数少
-
缺点:
-
对特定输入(如已排序数组)性能退化
- 不稳定排序(相等元素的相对顺序可能改变)
算法改进
1. 切换到插入排序
对于小规模子数组(通常小于5-15个元素),使用插入排序会更有效率:
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo + M) {
Insertion.sort(a, lo, hi);
return;
}
int j = partition(a, lo, hi);
sort(a, lo, j-1);
sort(a, j+1, hi);
}
2. 三取样切分
不总是选择第一个元素作为切分元素,而是从子数组中取3个元素,并使用其中值为切分元素。这样切分更加平衡,减少最坏情况的出现概率。
3. 三向切分
当数组中有大量重复元素时,三向切分的快速排序效率更高,将数组分成小于、等于和大于切分元素的三部分:
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int lt = lo, i = lo+1, gt = hi;
Comparable v = a[lo];
while (i <= gt) {
int cmp = a[i].compareTo(v);
if (cmp < 0) exch(a, lt++, i++);
else if (cmp > 0) exch(a, i, gt--);
else i++;
}
sort(a, lo, lt - 1);
sort(a, gt + 1, hi);
}
对于包含大量重复元素的数组,三向切分快速排序的复杂度可从O(n log n)降至O(n),成为"熵最优"排序算法,其复杂度更接近于输入数组的信息量。