原文链接
快速排序
快速排序即每次从数组中找出一个切分元素,然后将数组中大于它的元素移动到它的后面,将小于它的元素移动到它的前面。由此就确定了这个元素的位置,接下来只需要不断对它前后数组执行同样操作,最终便能确定每个元素的位置。
package cn.ywrby.sorts;
import cn.ywrby.tools.StopWatch;
import edu.princeton.cs.algs4.StdRandom;
public class QuickSort {
private static int partition(Comparable[] a, int lo, int hi) {
int i = lo, j = hi + 1;
Comparable v = a[lo];
while (true) {
while (less(a[++i], v)) if (i == hi) break;
while (less(v, a[--j])) if (j == lo) break;
if (i >= j) break;
exch(a, i, j);
}
exch(a, lo, j);
return j;
}
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 boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
private static void show(Comparable[] a) {
System.out.print("After sort : ");
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
public static boolean isSorted(Comparable[] a) {
for (int i = 0; i < a.length - 1; i++) {
if (!less(a[i], a[i + 1])) {
return false;
}
}
return true;
}
public static void main(String[] args) {
int N = 1000;
Comparable<Double>[] test = new Comparable[N];
System.out.print("before sort : ");
for (int i = 0; i < N; i++) {
double data = Math.random();
System.out.print(data + " ");
test[i] = data;
}
System.out.println();
StopWatch watch = new StopWatch();
sort(test);
double time = watch.elapsedTime();
assert isSorted(test);
show(test);
System.out.println("time=" + time);
}
}
快速排序优点
- 原地归并(只需要很小的辅助栈)
- 内循环比大多数排序算法都要少
- 排序所需时间为NlgN
快速排序的缺点是极其脆弱,许多错误都会导致性能降低到平方级别
保证快速排序稳定的条件
- 原地切分
- 避免越界,严格控制越界条件
- 保持随机性,除了代码中的将数组随机,还可以每次切分时,随机决定切分元素
- 终止循环,最常见的错误是没有考虑数组中可能存在与切分元素相同的元素
- 递归的终止条件严格控制
算法分析
最好情况下,运行情况是每次数组都能被对半分开,这种情况下快速排序的比较次数满足类似于归并排序的:
C_N=2C_{N/2}+N
通过归并排序的分析易知这种情况下算法的运行时间也是NlgN级别的
虽然我们不能保证每次切分后都使其落到中间位置,但最终结果是类似的
算法改进
在数组长度较小时(5~15)切换到插入排序
三取样切分:使用子数组的一小部分元素(3个)的中位数来切分数组,代价是需要计算中位数
三向切分的快速排序,在切分时将数组分成三段,大于,小于和等于。这种提高适用于重复元素很多的数组进行排序