[关闭]
@kuier1992 2015-09-06T16:03:44.000000Z 字数 4191 阅读 1558

排序学习

算法


排序就是将一组对象按照某种逻辑顺序重新排列的过程

游戏规则

排序算法模板API设计

返回值 方法名称 作用
void sort(Comparable[] a) 将a进行排序
boolean less(Comparable v, Comparable w) 对元素进行比较
void exch(Comparable[] a ,int i ,int j) 交换a中i和j元素的位置
void show(Comparable[] a) 打印a中元素
boolean isSorted(Comparable[] a) 指示元素是否有序)

排序算法模板类的实现

  1. public class Example{
  2. public static void sort(Comparable[] a){
  3. //后面补充实现
  4. }
  5. private static boolean less(Comparable v ,Comparable w){
  6. return v.compareTo(w) < 0;
  7. }
  8. private static void exch(Comparable[] a,int i ,int j){
  9. Comparable t = a[i];
  10. a[i] = a[j];
  11. a[j] = t;
  12. }
  13. private static void show(Comparable[] a){
  14. for(int i = 0; i < a.length ;i++){
  15. StdOut.print(a[i] + " ");
  16. }
  17. StdOut.println();
  18. }
  19. public static boolean isSorted(Comparable[] a){
  20. for(int i = 1 ; i < a.length ; i++){
  21. if(less(a[i],a[i-1]))
  22. return false;
  23. }
  24. return true;
  25. }
  26. public static void main(String[] args){
  27. String[] a = In.readString();
  28. sort(a);
  29. assert isSorted(a); //确认排序后数组元素都是有序的
  30. show(a);
  31. }
  32. }

选择排序

说明

首先找到数组中最小的那个元素,然后将它和数组中的第一个元素交换位置(如果第一个元素就是最小元素则和它自己交换位置)。其次在剩下的元素中找到最小元素,和数组中的第二个元素交换位置。如此反复,直至将整个数组排序。

命题A 对于长度为N的数组,选择排序需要大约N2次比较和N次交换

选择排序具有两个鲜明的特点:运行时间和输入无关数据移动是最少的

实现

  1. public class Selection{
  2. public static void sort(Comparable[] a){
  3. //将a[]按升序排列
  4. int N = a.length;
  5. for(int i = 0; i < N ; i++){
  6. int min = i;//最小元素的索引
  7. for(int j = i+1 ; j < N ; j++){
  8. if(less(a[j],a[min])){
  9. min = j ;
  10. }
  11. }
  12. exch(a,i,min);
  13. }
  14. }
  15. }

选择排序的轨迹
选择排序的轨迹

插入排序

说明

将每个元素插入到已有的有序的序列中

命题B 对于随机排列的长度为N且主键不重复的数组,平均情况需要N2/4次比较以及N2/4次交换。最坏情况下需要N2次比较和N2/2次交换,最好情况下需要N-1次比较和0次交换

算法实现

  1. public class Insertion{
  2. public static void sort(Comparable[] a){
  3. //将a[]按照升序排列
  4. int N = a.length;
  5. for(int i = 1; i < N ; i++){
  6. for(int j = i ; j >0 && less(a[j] , a[j-1]);j--){
  7. exch(a,j,j-1);
  8. }
  9. }
  10. }
  11. }

插入排序的轨迹
插入排序的轨迹

命题C 插入排序所需要的交换操作和数组中倒置的数量相同,需要的比较次数大于等于倒置的数量,小于等于倒置的数量加上数组的大小再减一

插入排序对于部分有序的数组非常高效,也很适合小规模数组

比较两种排序算法

性质D 对于随机排序的无重复主键的数组,插入排序和选择排序的运行时间是平方级别的,两者之比应该是一个较小的常数

针对给定输入,为本章中的一种排序算法计时

  1. public static double time(String alg,Comparable[] a){
  2. Stopwatch timer = new Stopwatch();
  3. if(alg.equals("Insertion"))
  4. Insertion.sort(a);
  5. if(alg.equals("Selection"))
  6. Selection.sort(a);
  7. if(alg.equals("shell"))
  8. Shell.sort(a);
  9. if(alg.equals("Merge"))
  10. Merge.sort(a);
  11. if(alg.equals("Quick"))
  12. Quick.sort(a);
  13. if(alg.equals("Heap"))
  14. Heap.sort(a);
  15. return timer.elapsedTime();
  16. }

比较两种排序算法

  1. public class SortCompare{
  2. public static double time(String alg,Double[] a){
  3. //见上文
  4. }
  5. public static double timeRandomInput(String alg, int N ,int T){
  6. //使用算法alg将T个长度为N的数组排序
  7. double total = 0.0 ;
  8. Double[] a = new Double[N] ;
  9. for(int t = 0 ; t< T;t++){
  10. //进行一次测试,生成一个数组并排序
  11. for(int i = 0; i < N ; i++){
  12. a[i] = StdRandom.uniform();
  13. }
  14. total +=time(alg,a);
  15. }
  16. return total;
  17. }
  18. public static void main(String[] args){
  19. String alg1 = args[0];
  20. String alg2 = args[1];
  21. int N = Integer.parseInt(args[2]);
  22. int T = Integer.parseInt(args[3]);
  23. double t1 = timeRandomInput(alg1,N,T);//算法1的总时间
  24. double t2 = timeRandomInput(alg2,N,T);
  25. StdOut.printf("For %d random Doubles\n %s is",N,alg1);
  26. StdOut.printf("%.0f times faster than %s\n",t2/t1,alg2);
  27. }
  28. }

希尔排序

说明

希尔排序为了加快速度简单的改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组进行排序

实现

  1. public class Shell{
  2. public static void sort(Comparable[] a ){
  3. //将a[]按升序排列
  4. int N = a.length;
  5. int h = 1;
  6. while(h<N/3)
  7. h = 3 * h + 1 ; // 1,4,13,40,121,364,1093......
  8. while( h >= 1 ){
  9. //将数组变为h有序
  10. for(int i = h ; i < N ;i++){
  11. //将a[i]插入到a[i-h],a[i-2*h],a[i-3*h]...之中
  12. for(int j = i ;j >= h && less(a[j],a[j-h]);j -= h )
  13. {
  14. exch(a, j , j - h);
  15. }
  16. h = h/3;
  17. }
  18. }
  19. }
  20. }

性质E 使用递增序列1,4,13,40,121,364...的希尔排序所需的比较次数不会超出N的若干乘以递增序列的长度。
希尔排序的运动轨迹
希尔排序的运动轨迹

归并排序

说明

优点 它能保证将任意长度为N的数组排序所需时间和NlogN成正比;缺点它所需的额外空间和N成正比

原地归并的抽象方法

  1. public static void merge(Comparable[] a , int lo ,int mid ,int hi){
  2. int i = lo ;
  3. int j = mid + 1 ;
  4. //将a[lo..hi]复制到aux[lo..hi]中
  5. for(int k = lo ;k <= hi ; k++){
  6. aux[k] = a[k];
  7. }
  8. for(int k = lo ; k<=hi ;k++){
  9. if(i>mid)
  10. a[k] = aux[j++];
  11. else if(j>hi)
  12. a[k] = aux[i++];
  13. else if(less(aux[j],aux[i]))
  14. a[k] = aux[j++];
  15. else
  16. a[k] = aux[i++];
  17. }
  18. }

记起来实在太麻烦了,先将所有元素复制到aux[]中。然后进行四个条件的判断,如果左边用尽(取右半边元素),右半边用尽(取左半边元素)、右半边元素小于左边元素(取右边元素)、右边元素大于左边元素(取左边元素)
原地归并的抽象方法的运动轨迹
原地归并的抽象方法的运动轨迹

自顶而下的归并排序

  1. public class Merge{
  2. private static Comparable[] aux;
  3. public static void sort(Comparable[] a){
  4. aux = new Comparable[a.length];
  5. sort(a,0,a.length-1);
  6. }
  7. private static void sort(Comparable[] a, int lo ,int hi){
  8. if(hi <= lo)
  9. return;
  10. int mid = lo + (hi - lo)/2;
  11. sort(a,lo,mid);//将左半边排序
  12. sort(a,mid+1,hi);//将右半边排序
  13. merge(a , lo ,mid ,hi)//归并结果
  14. }
  15. }

自顶向下的归并排序中归并结果的轨迹
自顶向下的归并排序中归并结果的轨迹
自顶向下的归并排序的调用轨迹
自顶向下的归并排序的调用轨迹

命题F对于长度为N的任意数组,自顶向下的归并排序需要1/2NlgNNlgN次比较

命题G对于长度为N的任意数组,自顶向下的归并排序最多需要访问数组6NlgN

快速排序

说明

特点是原地排序,且将长度为N的数组排序所需的时间和NlogN成正比,内循环比大多数排序算法都要短小

基本算法

  1. public class Quick{
  2. public static void sort(Comparable[] a){
  3. StdRandom.shuffle(a); //消除对输入的依赖
  4. sort(a , 0 , a.length-1 );
  5. }
  6. private static void sort(Comparable[] a, int lo ,int hi){
  7. if(hi <= lo) return;
  8. int j = partition(a , lo ,hi );//切分
  9. sort(a,lo,j-1);
  10. sort(a,j+1,hi);
  11. }
  12. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注