插入排序及其优化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/nghuyong/article/details/52651848

构造排序基础类

BaseSort.java

package sort;

import edu.princeton.cs.algs4.StdOut;

public  class BaseSort {

    //比较
    public static boolean less(Comparable a , Comparable b) {
        return a.compareTo(b)<0;
    }

    //交换顺序
    public static void exchange(Comparable[] a , int i, int j) {
        Comparable temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    //打印
    public static void show(Comparable[] a) {
        for (int i = 0; i < a.length; i++) {
            StdOut.print(a[i]+" ");
        }
        StdOut.println();
    }

    //判断是否有序
    public static boolean isSort(Comparable[] a ) {
        for (int i = 1; i < a.length; i++) {
            if(less(a[i], a[i-1]))
                return false;
        }
        return true;
    }

}

通过“交换”实现

public static void insertSortByExchange(Comparable[] a) {
        int N = a.length;
        for(int i=1;i<N;i++){
            for(int j=i;j>0&&less(a[j], a[j-1]);j--){
                exchange(a, j-1, j);
            }
        }
    }

假设元素a,左侧已经有序,将元素a向左侧依次检索,如果元素a大,就交换顺序,直到元素a较小或相等就跳出检索,说明a已经插入到合适的位置,完成插入排序。

通过“插入”实现

不通过交换插入的排序
参考可视化排序算法中的插入排序
伪代码:

mark first element as sorted
for each unsorted element
  'extract' the element
  for i = lastSortedIndex to 0
    if currentSortedElement > extractedElement
      move sorted element to the right by 1
    else: insert extracted element

简述之就是:先取出待插入的元素“extract”,在从右向左遍历已经排好的序列,如果extract较大就将有序的元素右移1位,否则就插入。
尝试使用代码实现中,要考虑如果插入位置是开头的特殊情况。

public static void insertSortByInsert(Comparable[] a) {
        int N = a.length;
        for (int i = 1; i < N; i++) {
        //取出元素
            Comparable extract = a[i];
            for (int j = i - 1; j >= 0; j--) {
                if (less(extract, a[j])) {
                    a[j + 1] = a[j];
                    //插入点为开头的特殊情况
                    if (j == 0) {
                        a[0] = extract;
                        break;
                    }
                } else {
                    a[j + 1] = extract;
                    break;
                }
            }
        }
    }

这种插入,待插入元素只在找到插入位置后一次插入,不需要不断向左侧 交换。

插入排序的哨兵

先找出序列中的最小元素并将其置于元素的最左侧,这样就可以去掉内循环中的判断条件j>0

这是一种常见的规避边界测试的方法,能够省略判断条件的元素被称为哨兵。

    public static void insertSortByGuard(Comparable[] a) {
        int N = a.length;
        //寻找哨兵
        int min = 0 ;
        for(int i=1;i<N;i++){
            if(less(a[i], a[min])){
                min = i;
            }
        }
        exchange(a, 0, min);
        for(int i=1;i<N;i++){
            for(int j=i;less(a[j], a[j-1]);j--){
                exchange(a, j-1, j);
            }
        }
    }

但实际上由于多了一个寻找最小值的过程总的来说并不能提高效率。

希尔排序

通过希尔排序可以间隔移动。这样可以将数列逐渐变成基本有序的状态,最终变成有序。

public static void shellSort(Comparable[] 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++){
                for(int j=i;j>=h&&less(a[j], a[j-h]);j-=h){
                    exchange(a, j, j-h);
                }
            }
            h=h/3;
        }
    }

算法效率对比

分别用上面的算法排序1000*1000个,即100万个double类型的数,进行排序。

//测试算法时间
    public static double sortRunTime(int N,int T,String sortName) {
        double total = 0.0;
        Double[] a = new Double[N];
        for(int t =0 ;t<T;t++){
            for(int i=0;i<N;i++)
                a[i] = StdRandom.uniform();
            Stopwatch timer = new Stopwatch();
            if(sortName.equals("insetSortByExchange")){
                insetSortByExchange(a);
            }
            if(sortName.equals("insertSortByGuard")){
                insertSortByGuard(a);
            }
            if(sortName.equals("shellSort")){
                shellSort(a);
            }
            if(sortName.equals("insertSortByInsert")){
                insertSortByInsert(a);
            }
            total += timer.elapsedTime();
        }
        return total;
    }
public static void main(String[] args) {
        System.out.println("insetSortByExchange:\t"+sortRunTime(1000, 1000, "insetSortByExchange"));
        System.out.println("insertSortByGuard:\t"+sortRunTime(1000, 1000, "insertSortByGuard"));
        System.out.println("insertSortByInsert:\t"+sortRunTime(1000, 1000, "insertSortByInsert"));
        System.out.println("shellSort:\t"+sortRunTime(1000, 1000, "shellSort"));

    }

这里写图片描述
可见希尔排序可以大大提高效率。
通过哨兵反而会降低效率,代码会简单。

下面测试希尔排序与插入排序(交换)的时间对比。
测试数组按照2的幂次增长。从128开始:
这里写图片描述
这里写图片描述
这里写图片描述
可见随着数组长度的增加,从1w个数往后,希尔排序的效率明显比普通插入排序要高很多。

没有更多推荐了,返回首页