美文网首页机器学习程序员
经典排序算法回顾

经典排序算法回顾

作者: 昵称真难选 | 来源:发表于2017-04-14 14:53 被阅读50次

    原文出自王艳涛的专栏转载请注明出处!

    一、选择排序(最简单的排序算法)

    思想:

    1. 找到数组中最小的元素,将他与数组的第一个元素交换位置(如果第一个元素就是最小元素就和自己交换);
    2. 在剩下的元素中找到最小元素,将他与数组的第二个元素交换位置。
    3. 依次重复,直到整个数组有序。
    private static void selectSort(int [] a){
            int N = a.length;
            for (int i = 0; i < N; i++ ) {
                int min =i;
                for(int j = i+1; j < N; j++){
                    if (a[j] < a[min]){
                        min = j;
                    }
                }
                int temp = a[i];
                a[i] = a[min];
                a[min] = temp;
            }
        }
    
    特点:
    • 当前索引左边元素都是有序的
    • 对于长队为N的数组,大约需要N次交换、(N平方)/2次比较;
    • 运行时间和输入无关
    • 数据移动是最少的

    二、插入排序

    思想:顺序地将未排序元素取出,插入到已排序的地元素中。

    private static void insertSort(int [] a){
            int N = a.length;
            for(int i = 1; i < N; i++){
                for(int j = i; j > 0 && a[j] < a[j-1]; j--){
                    int temp = a[j];
                    a[j] = a[j-1];
                    a[j-1] = temp;
                }
            }
        }
    
    特点:
    • 当前索引左边元素都是有序的;
    • 排序所需时间取决于输入时的初始顺序,对部分有序或者接近有序的数组排序较快;
    • 对于长度为N的不存在重复元素的数组,平均情况下比较(N平方)/4次、交换(N平方)/4,最坏情况下比较(N平方)/2次、交换(N平方)/2次,最好情况下,比较N-1次,交换0次。

    改进思想:内循环中较大的元素向右移动,不用每次都交换两个元素,可以减少访问数组的次数。

    private static void insertSort2(int [] a){
            int N = a.length;
            for(int i = 1; i < N; i++){
                int temp = a[i];
                int j;
                for(j = i; j > 0 && temp < a[j-1]; j--){
                    a[j] = a[j-1];
                }
                a[j] = temp;
            }
        }
    

    三、希尔排序

    思想:构建h有序数组,在插入排序的基础上加入一个外循环将h按照递增序列递减,当h为1时,数组将排序完毕。

    private static void shellSort(int [] a){
            int N = a.length;
            int h = 1;
            while(h < N/3){
                h = h*3 + 1;
            }
            while(h >= 1){
                for(int i = h ; i < N; i++){
                    int temp = a[i];
                    int j;
                    for(j = i; j >= h && temp < a[j-h]; j -= h){
                        a[j] = a[j-h];
                    }
                    a[j] = temp;
                }
                h = h/3;
            }
        }
    
    特点:
    • 对于大规模数组排序适用
    • 排序耗时不到平方级别,比较次数与N的二分之三次方成正比。

    四、归并排序

    思想:递归的将数组分成两半分别排序,然后将结果归并起来。

    0.归并方法:
        private static int []aux;
        public static void main (String[] args) throws java.lang.Exception
        {
            int [] a ={3,5,6,8,9,0,1,2,4,7};//前五个元素有序,后五个元素有序
            aux = new int[a.length];
            merge(a, 0, 4,9);
        }
    
    private static void merge(int [] a, int lo, int mid, int hi){
            int i = lo, j = mid+1;
            for(int k = lo; k <=hi; k++){
                aux[k] = a[k];//aux为外部定义的辅助数组,大小和数组a相同
            }
            for(int k = lo; k <= hi; k++){
                if(i > mid) a[k] = aux[j++];
                else if (j > hi) a[k] = aux[i++];
                else if (aux[j] < aux[i]) a[k] = aux[j++];
                else a[k] = aux[i++];
            }
        }
    
    1.自顶向下的归并排序:
      private static void mergeSort(int []a, int lo, int hi){
        if(hi <= lo) return;
        int mid = lo + (hi - lo)/2;
        mergeSort(a, lo, mid);
        mergeSort(a, mid+1, hi);
        merge(a, lo, mid, hi);//代码见“0.归并方法”中的merge方法
      }
    
    特点:
    • 对于超大规模数组排序适用;
    • 对于长度为N的任意数组,比较次数为(1/2)NlgN至NlgN之间,访问数组的次数最多为6NlgN;
    • 缺点:辅助数组使用的额外空间和N的大小成正比。
    改进1:

    由于递归回事小规模问题中的方法调用过于频繁,所以对小规模数组使用插入排序的方式,可以降低排序耗时。

      private static void mergeSort2(int []a, int lo, int hi){
        if(hi <= lo) return;
        int mid = lo + (hi - lo)/2;
        if((hi - lo)/2 <=3){//当数组长度不大于3时,启用插入排序。
          insertSort3(a, lo, lo+mid);
          insertSort3(a, lo+mid+1, hi);
        }else{
          mergeSort(a, lo, mid);
          mergeSort(a, mid+1, hi);
        }
        merge(a, lo, mid, hi);
      }
    
      private static void insertSort3(int [] a, int lo, int hi){
            int N = hi - lo;
            for(int i = 1; i <= N; i++){
                int temp = a[lo + i];
                int j;
                for(j = lo + i; j > 0 && temp < a[j-1]; j--){
                    a[j] = a[j-1];
                }
                a[j] = temp;
            }
        }
    
    改进2:

    如果a[mid]<a[mid+1],说明数组已经有序,可以跳过归并方法,减少耗时。

      private static void mergeSort3(int []a, int lo, int hi){
        if(hi <= lo) return;
        int mid = lo + (hi - lo)/2;
        if((hi - lo)/2 <=2){
          insertSort3(a, lo, lo+mid);
          insertSort3(a, lo+mid+1, hi);
        }else{
          mergeSort(a, lo, mid);
          mergeSort(a, mid+1, hi);
        }
        if(a[mid] <= a[mid+1]) return;//数组已有序,跳过merge方法
        merge(a, lo, mid, hi);
      }
    
    改进3:

    通过merge方法中交换a与aux的角色,省去for循环复制a到aux,节省耗时。

        private static int []aux;
        public static void main (String[] args) throws java.lang.Exception
        {
            int [] a ={3,2,6,1,4,9,0,5,7,8};
            aux = new int[a.length];
            mergeSort4(a, 0, 9);
            a = aux;
        }
      private static void mergeSort4(int []a, int lo, int hi){
        if(hi <= lo) return;
        int mid = lo + (hi - lo)/2;
        if((hi - lo)/2 <=2){
          insertSort3(a, lo, lo+mid);
          insertSort3(a, lo+mid+1, hi);
        }else{
          mergeSort(a, lo, mid);
          mergeSort(a, mid+1, hi);
        }
        if(a[mid] <= a[mid+1]) return;//数组已有序,跳过merge方法
        merge2(a, lo, mid, hi);
      }
    
      private static void merge2(int [] a, int lo, int mid, int hi){
            int i = lo, j = mid+1;
            for(int k = lo; k <= hi; k++){
                //交换a与aux角色
                if(i > mid) aux[k] = a[j++];
                else if (j > hi) aux[k] = a[i++];
                else if (a[j] < a[i]) aux[k] = a[j++];
                else aux[k] = a[i++];
            }
        }
    
    2.自底向上的归并排序:
      private static void mergeSortBU(int []a){
        int N = a.length;
        for(int sz = 1; sz < N; sz =sz +sz){
          for (int lo = 0; lo < N - sz; lo += sz +sz) {
            merge(a, lo, lo+sz-1, Math.min(lo + sz +sz-1, N-1));
          }
        }
      }
    

    五、快速排序

    思想:递归的切分数组,使切分点左边都小于切分点元素,切分点右边都大于切分点元素,最终整个数组有序。
    切分后的数组具有三个特点:

    • 对于切分点j,a[j]元素位置已经排定
    • a[lo]到a[j-1]的元素都小于a[j]
    • a[j+1]到a[hi]的元素都大于a[j]
    0.切分方法:
      private static int partition(int []a, int lo, int hi){
        int i = lo, j= hi+1;
        int v = a[lo];
        while(true){
          while(a[++i] < v) if(i == hi) break;
          while(v < a[--j]) if(j == lo) break;
          if( i >= j) break;
          int temp = a[j];
          a[j] = a[i];
          a[i] = temp;
        }
        int temp = a[j];
        a[j] = a[lo];
        a[lo] = temp;
        return j;
      }
    
    1.快速排序:
    private static void quickSort(int []a, int lo, int hi){
      if (hi <= lo) return;
      int j = partition(a, lo, hi);
      quickSort(a, lo, j-1);
      quickSort(a, j+1, hi);
    }
    
    特点:
    • 平均需要大约2NlnN次比较,最坏需要NN/2次比较,但是随即打乱数组可以避免最坏情况的出现。
    改进:

    由于递归回事小规模问题中的方法调用过于频繁,况且对小规模数组使用插入排序的方式更快。
    一般情况下,小数组的长度定义在5-15之间效果较好

      private static void quickSort2(int []a, int lo, int hi){
        if (hi <= lo + 3){//数组长度不大于3(最好5-15)时,使用插入排序
          insertSort3(a, lo, hi);
          return;
        }
        int j = partition(a, lo, hi);
        quickSort2(a, lo, j-1);
        quickSort2(a, j+1, hi);
      }
    
    2.三向切分快速排序:

    思想:从左到右遍历数组,维护一个指针lt使a[lo...lt-1]都小于v,一个指针gt使a[gt+1...hi]都大于v,一个指针i使a[lt...i-1]都等于v,a[i...gt]尚未确定。

      private static void quickSort3(int []a, int lo, int hi){
        if(hi <= lo) return;
        int lt = lo, i = lo+1, gt =hi;
        int v = a[lo];
        while(i <= gt){
          if(a[i] < v){
            int temp = a[lt];
            a[lt] = a[i];
            a[i] = temp;
            lt++;
            i++;
          }else if (a[i] > v) {
            int temp = a[gt];
            a[gt] = a[i];
            a[i] = temp;
            gt--;
          }else{
            i++;
          }
        }
        quickSort3(a, lo, lt-1);
        quickSort3(a, gt+1, hi);
      }
    
    特点:对于存在大量重复元的数组,比标准快速排序效率高得多。

    相关文章

      网友评论

        本文标题:经典排序算法回顾

        本文链接:https://www.haomeiwen.com/subject/vghbattx.html