美文网首页
算法入门教程-希尔排序

算法入门教程-希尔排序

作者: 会上树的程序猿 | 来源:发表于2020-02-13 20:47 被阅读0次

上节我们学习了插入排序,其实插入排序存在一些小的缺陷,还是不够完美,所以有人在它的基础上进行了完善,.由于完善的人叫希尔,故此算法的思想叫希尔排序算法思想,为啥说插入排序有缺陷了,我们来简单的看一个例子:

  • 假如我有这样一个待排序的数组如: int [] arr = {2,3,4,5,6,1},通过之前的分步推导我们来看排序的过程:
  • 第一轮排序的代码如下:
  //逐步推导过程
    //{2,3,4,5,6,1} -> 首先以{2}为有序列表,以{3,4,5,6,1}为无序列表
    //将无序列表中的数往有序列表中添加
    //定义待插入的数和索引下标
    int insertVal = arr[1]; //这里也就是3
    int insertIndex = 1 - 1; //arr[1]前面数的下标

    //简单说明:
    //1. insertIndex>=0 其主要的目的是为了防止在插入时候为了约束越界
    //2. insertVal < arr[insertIndex]表示待插入的数还没有找到插入的位置
    //3. 需要将 arr[insertIndex]往后移动
    while (insertIndex >= 0 && insertVal < arr[insertIndex]) {

        arr[insertIndex + 1] = arr[insertIndex]; 
        insertIndex--;
    }
    //当退出while循环时,说明插入的位置找到,需要 insertIndex +1 如: 34改为 134
        arr[insertIndex + 1] = insertVal;
    System.out.println("第一轮插入后的结果为:");
    System.out.println(Arrays.toString(arr));

由于上述给的排序的数组的特殊性,其实我们的while循环是不进去的,只需要因为将无序列表的3插入有序列表的{2}中时,3>2,所以插入到有序列表{2}的后面即{2,3}来看测试结果:

第一轮排序结果.png

跟我们预想的结果一样,接着看:

  • 第二轮排序过程:
 //定义待插入的数和索引下标
     insertVal = arr[2]; 
     insertIndex = 2-1; 
    //简单说明:
    //1. insertIndex>=0 其主要的目的是为了防止在插入时候为了约束越界
    //2. insertVal < arr[insertIndex]表示待插入的数还没有找到插入的位置如:
    //3. 需要将 arr[insertIndex]往后移动
    while (insertIndex >=0 && insertVal < arr[insertIndex]){

        arr[insertIndex +1] = arr[insertIndex]; 
        insertIndex --;
    }
    //当退出while循环时,说明插入的位置找到,需要 insertIndex +1 
    arr[insertIndex + 1] = insertVal;
    System.out.println("第二轮插入后的结果为:");
    System.out.println(Arrays.toString(arr));

分析过程跟第一轮的一样,我们来看结果:

第二轮排序结果.png
  • 第三轮排序过程
 //定义待插入的数和索引下标
    insertVal = arr[3]; //这里也就是119
    insertIndex = 3-1; //arr[2]前面数的下标
    //简单说明:
    //1. insertIndex>=0 其主要的目的是为了防止在插入时候为了约束越界
    //2. insertVal < arr[insertIndex]表示待插入的数还没有找到插入的位置如: 34 < arr[] =101
    //3. 需要将 arr[insertIndex]往后移动
    while (insertIndex >=0 && insertVal < arr[insertIndex]){

        arr[insertIndex +1] = arr[insertIndex]; //此时的数组为{101,101,119,1}
        insertIndex --;
    }
    //当退出while循环时,说明插入的位置找到,需要 insertIndex +1 如: 34改为 134
    arr[insertIndex + 1] = insertVal;
    System.out.println("第三轮插入后的结果为:");
    System.out.println(Arrays.toString(arr));

直接看结果:

第三轮排序结果.png
  • 第四轮排序过程
 //定义待插入的数和索引下标
    insertVal = arr[4]; //这里也就是119
    insertIndex = 4-1; //arr[2]前面数的下标
    //简单说明:
    //1. insertIndex>=0 其主要的目的是为了防止在插入时候为了约束越界
    //2. insertVal < arr[insertIndex]表示待插入的数还没有找到插入的位置如: 34 < arr[] =101
    //3. 需要将 arr[insertIndex]往后移动
    while (insertIndex >=0 && insertVal < arr[insertIndex]){

        arr[insertIndex +1] = arr[insertIndex]; //此时的数组为{101,101,119,1}
        insertIndex --;
    }
    //当退出while循环时,说明插入的位置找到,需要 insertIndex +1 如: 34改为 134
    arr[insertIndex + 1] = insertVal;
    System.out.println("第四轮插入后的结果为:");
    System.out.println(Arrays.toString(arr));

来看测试结果:

第四轮测试结果.png
  • 第五轮排序过程:
 //定义待插入的数和索引下标
    insertVal = arr[5]; //这里也就是119
    insertIndex = 5-1; //arr[2]前面数的下标
    //简单说明:
    //1. insertIndex>=0 其主要的目的是为了防止在插入时候为了约束越界
    //2. insertVal < arr[insertIndex]表示待插入的数还没有找到插入的位置如: 34 < arr[] =101
    //3. 需要将 arr[insertIndex]往后移动
    while (insertIndex >=0 && insertVal < arr[insertIndex]){

        arr[insertIndex +1] = arr[insertIndex]; //此时的数组为{101,101,119,1}
        insertIndex --;
    }
    //当退出while循环时,说明插入的位置找到,需要 insertIndex +1 如: 34改为 134
    arr[insertIndex + 1] = insertVal;
    System.out.println("第五轮插入后的结果为:");
    System.out.println(Arrays.toString(arr));

通过五步我们完成了对它的排序过程,期待吧,心累:

第五轮排序结果.png

其实我们也能发现问题,我们要对1进行排序时需要移动的次数太频繁,这样是很耗时的一个操作,当然会影响我们的效率,所以著名的希尔提出了新的算法思想对它进行了优化操作---------->希尔排序思想,首先我们来对它进行一个简单的介绍

希尔排序的介绍

我们都说了,希尔排序排序的本质是插入排序,由希尔本人在1959年提出的算法思想,是对插入排序的完善后的一个高效版本的插入排序,同时也称作为缩小增量排序.

我们通过一张示意图来了解下希尔[排序的过程和思想

希尔排序示意图.png

简单的来说一下上述图解的过程

  • 首先我们将上述数组分成步长为5,也就是5组数组,就像图中的一样,同一种颜色为一组如[8,3]为一组.
  • 接着对每一组的数据进行插入排序,将小的数字排到了前面,然后在缩小步长也就是5/2 = 2,将数组分成了2组也就是图中所示
  • 对步长为2的数组进行插入排序排序之后,继续分组为 2/2 =1 ,也就是图中最后一个我们将数组分为了一组最后进行排序即可.

上述的过程每一次都再缩小步长,我们可以发现从5---->2------>1,最后完成排序,简单的对希尔排序算法的思想进行总结

希尔排序算法的思想

希尔排序就是将一组待排序的数字按下标进行一定增量的分组,对每组直接使用插入排序的算法,随着增量的减少,每组包含的数字也越来越多,当增量减至1时,整个列表被分为一组,也就是我们算法的停止.
假设我有一组待排序的数字如: int [] arr = {8,9,1,7,2,3,5,4,6,0},我们通过代码的方式来完成对它的排序:

代码实现希尔排序算法

  • 第一轮的排序实现过程
 //第一轮排序
    //首先将10个数据分成5组了
    int temp = 0;
    for (int i = 5; i<arr.length; i++){
        //内循环的说明:
        //遍历各组中所有的元素(总共5组,每组有2个元素),步长为5如 8 和3为一组,9和5为一组等等
        for (int j = i -5; j >=0; j-=5){
            //如果当前元素大于+步长后的那个元素,则进行位置的交换
            if (arr[j] > arr[j+5]){
                temp =arr[j];
                arr[j] = arr[j+5];
                arr[j+5] = temp;
            }
        }
    }
    System.out.println("第一轮的排序结果为:");
    System.out.println(Arrays.toString(arr));
  • 来看测试结果:
希尔排序第一轮测试结果.png
  • 第二轮的代码实现过程
//第二轮排序
    //在前面5组的基础上进行分5/2 = 2组
    for (int i = 2; i<arr.length; i++){
        //内循环的说明:
        //遍历各组中所有的元素(总共5组,每组有2个元素),步长为5如 8 和3为一组,9和5为一组等等
        for (int j = i -2; j >=0; j-=2){
            //如果当前元素大于+步长后的那个元素,则进行位置的交换
            if (arr[j] > arr[j+2]){
                temp =arr[j];
                arr[j] = arr[j+2];
                arr[j+2] = temp;
            }
        }
    }
    System.out.println("第二轮的排序结果为:");
    System.out.println(Arrays.toString(arr));
  • 来看测试结果:


    希尔排序第二轮测试结果.png
  • 第三轮的代码实现过程
 //第三轮排序
    //因为是第三轮排序,我们将10个数字分为2/2 = 1组
    for (int i = 1; i<arr.length; i++){
        //内循环的说明:
        //遍历各组中所有的元素(总共5组,每组有2个元素),步长为5如 8 和3为一组,9和5为一组等等
        for (int j = i -1; j >=0; j-=1){
            //如果当前元素大于+步长后的那个元素,则进行位置的交换
            if (arr[j] > arr[j+1]){
                temp =arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
    System.out.println("第三轮的排序结果为:");
    System.out.println(Arrays.toString(arr));
  • 来看测试结果


    希尔排序第三轮测试结果.png

之前我们也通过图来了解了该过程,通过三轮的排序我们完成了对该数组的数字从小到大的排序过程,我们对代码来优化下.

代码优化

//希尔排序方法
public static void shellSort(int[] arr){

   //定义一个临时的变量来储存交换的元素
    int temp = 0;
    int count = 0;
    for (int gap = arr.length /2;gap>0; gap /= 2){
        for (int i = gap; i<arr.length; i++){
            //内循环的说明:
            //遍历各组中所有的元素(总共5组,每组有2个元素),步长为gap如 8 和3为一组,9和5为一组等等
            for (int j = i -gap; j >=0; j-=gap){
                //如果当前元素大于+步长后的那个元素,则进行位置的交换
                if (arr[j] > arr[j+gap]){
                    temp =arr[j];
                    arr[j] = arr[j+gap];
                    arr[j+gap] = temp;
                }
            }
        }
        count ++;
        System.out.println("第"+count+"轮的排序结果为:");
        System.out.println(Arrays.toString(arr));

    }

上述代码是采用交换的一种方式来完成排序,也就是说我们每发现一个就立马进行交换,这种写法其实也没毛病,我们来看下另外一种(移位法)来实现,代码如下,大家可以比较两个算法的好与不好:

  • 移位法
//方法优化(移位法)
public static void shellSort2(int [] arr){
        //gap为步长
    for (int gap = arr.length /2; gap >0 ; gap /=2) {
        //从gap个元素开始,对其所在组进行插入排序
        for (int i = gap; i < arr.length ; i++) {
            int j = i; //交换变量
            int temp = arr[j];
            //表示需要进行位置的交换
            if (arr[j] < arr[j - gap] ){
                while (j -gap >=0 && temp < arr[j-gap]){
                    //移动
                    arr[j] = arr[j-gap];
                    j -= gap;
                }
                //当退出循环时,表示给temp找到了插入的位置
                arr[j] = temp;
            }

        }
    }
}

这里就不测试了,我们最后来看下希尔排序的执行时间如何,同样是10W数据的排序,代码如下:

 //插入的时间复杂度测试
    int [] arr = new int[100000];
    for (int i = 0; i < arr.length; i++) {
        arr[i] = (int)(Math.random() * 100000); //随机生成[0,100000)的数
    }

    Date date1 = new Date();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String format = dateFormat.format(date1);
    System.out.println("排序前的时间为:"+format);
    //进行排序
    shellSort(arr);
    Date date2 = new Date();
    SimpleDateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String format2 = dateFormat.format(date2);
    System.out.println("排序后的时间为:"+format2);
}
  • 测试结果如下:
交换法的执行时间.png

来看下移位法的执行时间

移位法的执行时间结果.png

从上图我们可以看出两个方式算法的执行效率,那么关于希尔算法的学习就到这里了....

相关文章

  • 排序算法(四) 希尔排序(插入排序的进化)

    参考Java排序算法(四):希尔排序常见排序算法 - 希尔排序 (Shell Sort) 希尔排序算法是按其设计者...

  • 排序算法(二)之希尔排序

    图解排序算法(二)之希尔排序 希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也...

  • 排序-希尔排序(分治)

    希尔排序 希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。 希尔排序...

  • swift经典算法-希尔排序

    希尔排序 希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。 希尔排序...

  • 排序算法④——希尔排序

    希尔排序 希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。 希尔排序...

  • java实现快速排序、归并排序、希尔排序、基数排序算法...

    快速排序算法 归并排序算法 希尔排序算法 基数排序算法

  • 希尔排序学习

    希尔排序希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。希尔排序是基...

  • 排序算法-7---希尔排序

    排序算法-7---希尔排序 概念 希尔排序(Shellsort),也称递减增量排序算法,是一种典型的插入排序算法,...

  • 希尔排序

    希尔排序一种是很常见的排序算法,该算法在1959年由Donald Shell公布。 希尔排序的奥妙 1、希尔排序的...

  • 希尔排序

    希尔排序 (Shell Sort) 算法原理 希尔排序算法是按其设计者希尔(Donald Shell)的名字命名,...

网友评论

      本文标题:算法入门教程-希尔排序

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