首页 > 代码库 > 排序---快速排序
排序---快速排序
写在前面的话:
一枚自学Java和算法的工科妹子。
- 算法学习书目:算法(第四版) Robert Sedgewick
- 算法视频教程:Coursera Algorithms Part1&2
本文是根据《算法(第四版)》的个人总结,如有错误,请批评指正。
一、快速排序介绍
快速排序(Quicksort)是一种分治的排序算法。它将一个数组分成两个数组,将两部分独立地排序,快速排序示意图如下:
与归并排序的区别:
- 归并排序将一个数组等分为两半,快速排序中切分数组的位置取决于数组的内容;
- 归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序,快速排序将数组排序的方式是当两个子数组都有序时整个数组也就有序了;
- 归并排序中递归调用发生在处理整个数组之前,快速排序中递归调用发生在处理整个数组之后。
快速算法引人注目的特点就是(1)原地排序;(2)长度N的数组排序所需时间和NlgN成正比,它的内循环比大多数排序算法都要短小,使得排序速度很快。但是主要缺点是非常脆弱,在实现时要非常小心,保持数组的随机性,将输入的待排序数组元素的顺序先打乱过,否则对于已经排好序或是逆序的数组,排序算法需要~N2/2次比较,即运行时间是平方级别的。为了预防这种情况的方法,可以用StdRandom.shuffle()方法打乱数组,这个方法在许多场合会用到,大家可以学习下。
1 public class StdRandom 2 {// 这是一个随机数静态方法库,包含各种随机数生成方法和随机数操作方法 3 ... 4 public static void shuffle(int[] a) { 5 // 参数可以改成其他数据类型double 6 if (a == null) throw new IllegalArgumentException("argument array is null"); 7 int n = a.length; 8 for (int i = 0; i < n; i++) { 9 int r = i + StdRandom.uniform(n-i); 10 //StdRandom.uniform(n-i) 方法随机产生1到n-i之间的整数 11 int temp = a[i]; 12 a[i] = a[r]; 13 a[r] = temp; 14 } 15 } 16 }
二、切分(paetition)
快速排序算法的关键在于切分(paetition),这个过程使得数组满足下面三个条件:
- 对于某个j,a[j]已经排定;
- a[lo]到a[j-1]中的所有元素都不大于a[j];
- a[j+1]到a[hi]中的所有元素都不小于a[j]。
1. 切分(paetition)流程:
- 选取a[lo]作为切分元素;
- 从数组左端向右扫描找到一个大于等于a[lo]的元素,从数组右端开始向左扫描找到一个小于等于a[lo]的元素,交换两元素位置;
- 重复步骤(2)就可以保证下图中左指针i左侧的元素不大于a[lo],右指针j右侧的元素不小于a[lo];
- 当两指针相遇时,将a[lo]和左子数组最右侧的元素交换。
2.切分(paetition)代码实现:
1 private static int partition(Comparable[] a, int lo, int hi) { 2 int i = lo, int j = hi + 1; // 左右扫描指针 3 Comparable v = a[lo]; // 切分元素 4 while (true) 5 { // 扫描左右,检查扫描是否结束并交换元素 6 while (less(a[++i], v)) if (i == hi) break; 7 while (less(v, a[--j]))if (j == lo) break; // 条件(j == lo) 冗余 8 if (i >= j) break; 9 exch(a, i, j); 10 } 11 12 // 将切分元素v放在争取位置a[j] 13 exch(a, lo, j); 14 // 完成a[lo .. j-1] <= a[j] <= a[j+1 .. hi] 15 return j; 16 }
上述代码的优势:
- 在原地切分,没有使用辅助数组;
- 左侧扫描遇到大于等于切分元素值时停下,右侧扫描遇到小于等于切分元素值时停下,尽管这样可能会产生一些不必要的交换,但这样做能避免算法的运行时间变成平方级别。
3.切分(paetition)案例图:
三、快速排序算法的实现
1 public class Quick { 2 3 private Quick() { } 4 5 public static void sort(Comparable[] a) { 6 StdRandom.shuffle(a); //消除对输入的依赖 7 sort(a, 0, a.length - 1); 8 assert isSorted(a); 9 } 10 11 // quicksort the subarray from a[lo] to a[hi] 12 private static void sort(Comparable[] a, int lo, int hi) { 13 if (hi <= lo) return; 14 int j = partition(a, lo, hi); 15 sort(a, lo, j-1); 16 sort(a, j+1, hi); 17 assert isSorted(a, lo, hi); 18 } 19 20 // partition the subarray a[lo..hi] so that a[lo..j-1] <= a[j] <= a[j+1..hi] 21 // and return the index j. 22 private static int partition(Comparable[] a, int lo, int hi) { 23 int i = lo, int j = hi + 1; 24 Comparable v = a[lo]; 25 while (true) { 26 while (less(a[++i], v)) if (i == hi) break; 27 while (less(v, a[--j])) if (j == lo) break; 28 if (i >= j) break; 29 exch(a, i, j); 30 } 31 32 exch(a, lo, j); 33 return j; 34 } 35 36 37 public static Comparable select(Comparable[] a, int k) { 38 if (k < 0 || k >= a.length) { 39 throw new IndexOutOfBoundsException("Selected element out of bounds"); 40 } 41 StdRandom.shuffle(a); 42 int lo = 0, hi = a.length - 1; 43 while (hi > lo) { 44 int i = partition(a, lo, hi); 45 if (i > k) hi = i - 1; 46 else if (i < k) lo = i + 1; 47 else return a[i]; 48 } 49 return a[lo]; 50 } 51 52 private static boolean less(Comparable v, Comparable w) { 53 return v.compareTo(w) < 0; 54 } 55 56 private static void exch(Object[] a, int i, int j) { 57 Object swap = a[i]; a[i] = a[j]; a[j] = swap; 58 } 59 60 private static boolean isSorted(Comparable[] a) { 61 return isSorted(a, 0, a.length - 1); 62 } 63 64 private static boolean isSorted(Comparable[] a, int lo, int hi) { 65 for (int i = lo + 1; i <= hi; i++) 66 if (less(a[i], a[i-1])) return false; 67 return true; 68 } 69 70 private static void show(Comparable[] a) { 71 for (int i = 0; i < a.length; i++) { 72 StdOut.println(a[i]); 73 } 74 } 75 }
快速排序的性能分析:
将长度为N 的数组排序,快速排序平均需要~2NlnN次数组比较,最好情况下是NlgN次数组比较,证明如下:令CN为将N个元素排序平均所需的比较次数
四、快速排序算法的改进
1.切换到插入算法
对于小数组,快速排序比插入排序慢,因为递归,快速排序的sort()方法在小数组中也会调用自己。改进方法:
将sort()中的语句if (hi <= lo) return; 替换成 if (hi <= lo+M) {Insertion.sort(a, lo, hi); return};。
2.三取样切分
使用子数组的一小部分元素的中位数来切分数组。
3.熵最优的排序
将数组切分成三部分,分别对应小于、等于和大于切分元素的数组元素;
Dijkstra“三向切分的排序”:从左到右遍历数组一次,维护一个指针lt使得a[lo..lt-1]中所有元素小于v, 指针gt使得 a[gt+1..hi]中所有元素大于v,指针 i使得a[lt..i-1] 中所有元素等于v,a[i..gt]中的元素都还不确定,方法流程:
- a[i] 小于 v: 交换a[lt]和a[i],将lt 和 i 加一;
- a[i] 大于 v: 交换a[i]和a[gt],将gt减一;
- a[i] 等于 v: 将 i加一。
1 public class Quick3way { 2 3 private Quick3way() { } 4 5 public static void sort(Comparable[] a) { 6 StdRandom.shuffle(a); 7 sort(a, 0, a.length - 1); 8 assert isSorted(a); 9 } 10 11 // quicksort the subarray a[lo .. hi] using 3-way partitioning 12 private static void sort(Comparable[] a, int lo, int hi) { 13 if (hi <= lo) return; 14 int lt = lo, gt = hi; 15 Comparable v = a[lo]; 16 int i = lo; 17 while (i <= gt) { 18 int cmp = a[i].compareTo(v); 19 if (cmp < 0) exch(a, lt++, i++); 20 else if (cmp > 0) exch(a, i, gt--); 21 else i++; 22 } 23 24 // a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]. 25 sort(a, lo, lt-1); 26 sort(a, gt+1, hi); 27 assert isSorted(a, lo, hi); 28 } 29 30 private static boolean less(Comparable v, Comparable w) { 31 return v.compareTo(w) < 0; 32 } 33 34 private static void exch(Object[] a, int i, int j) { 35 Object swap = a[i];a[i] = a[j];a[j] = swap; 36 } 37 38 private static boolean isSorted(Comparable[] a) { 39 return isSorted(a, 0, a.length - 1); 40 } 41 42 private static boolean isSorted(Comparable[] a, int lo, int hi) { 43 for (int i = lo + 1; i <= hi; i++) 44 if (less(a[i], a[i-1])) return false; 45 return true; 46 } 47 48 private static void show(Comparable[] a) { 49 for (int i = 0; i < a.length; i++) { 50 StdOut.println(a[i]); 51 } 52 } 53 }
作者: 邹珍珍(Pearl_zhen)
出处: http://www.cnblogs.com/zouzz/
声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出 原文链接 如有问题, 可邮件(zouzhenzhen@seu.edu.cn)咨询.
排序---快速排序