作者前言

大家好,我是阿濠,今篇内容跟大家分享的是排序算法之希尔排序,很高兴分享到segmentfault与大家一起学习交流,初次见面请大家多多关照,一起学习进步.

一、希尔排序的介绍

基本介绍

希尔排序是希尔(Donald Shell) 于1959年提出的一种排序算法。希尔排序也是一种插入排序它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序

基本思想

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止.

发现简单插入排序存在的问题

我们看简单的插入排序可能存在的问题.
数组arr = {2,3,4,5,6,1},这时插入的数:1(最小)

这样的过程是:
{2,3,4,5,6,6}//6往后移
{2,3,4,5,5,6}//5往后移
{2,3,4,4,5,6}//4往后移 
{2,3,3,4,5,6}//3往后移
{2,2,3,4,5,6}//2往后移
{1,2,3,4,5,6}//插入数1

结论:当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响,这时希尔排序作者,在根据插入排序进行改进优化。

希尔排序法示意图

图片.png

二、通过应用示例认识希尔排序

有一群小牛,考试成绩分别是{8,9,1,7,2,3,5,4,6,0},请从小到大排序.请分别使用
1.希尔排序时,对有序序列在插入时采用交换法,并测试排序速度
2.希尔排序时,对有序序列在插入时采用移动法,并测试排序速度

图片.png

希尔排序采用交换法

//希尔排序第一轮
//第一轮将十个数据分成五组
int temp=0;
//i=5 是数arr.length/2 步长为5
for (int i = 5; i < arr.length; i++) {
    //遍历各组中所有的元素(共5组,每组有2个元素),步长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("希尔排序第一轮结果"+ Arrays.toString(arr));

运行结果如下:
希尔排序第一轮结果:[3,5,1,6,0,8,9,4,7,2]
//第二轮
//i=2 是数5/2  步长为2
for (int i = 2; i < arr.length; i++) {
    //遍历各组中所有的元素(共2组,每组有5个元素),步长2
    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("希尔排序第二轮结果"+ Arrays.toString(arr));

运行结果如下:
希尔排序第一轮结果:[3,5,1,6,0,8,9,4,7,2]
希尔排序第二轮结果:[0,2,1,4,3,5,7,6,9,8]
//第三轮
//i=1 是数2/2  步长为1
for (int i =1; i < arr.length; i++) {
    //遍历各组中所有的元素(共1组,每组有10个元素),步长1
    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("希尔排序第三轮结果"+ Arrays.toString(arr));

运行结果如下:
希尔排序第一轮结果:[3,5,1,6,0,8,9,4,7,2]
希尔排序第二轮结果:[0,2,1,4,3,5,7,6,9,8]
希尔排序第三轮结果:[0,1,2,3,4,5,6,7,8,9]

规律已经出来了,变化排序元素、比较交换相似,改变的是每次分组的步数交换,即进行如下代码抽整:

int temp=0;
int count=0;
for(int gap=arr.length/2;gap>0;gap /= 2){

    for (int i = gap; i < arr.length; i++) {
        //遍历各组中所有的元素(gap,),步长gap
        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;
            }
        }
    }
    System.out.println("希尔排序第"+(++count)+"轮结果"+ Arrays.toString(arr));
}

运行结果如下:
希尔排序第1轮结果:[3,5,1,6,0,8,9,4,7,2]
希尔排序第2轮结果:[0,2,1,4,3,5,7,6,9,8]
希尔排序第3轮结果:[0,1,2,3,4,5,6,7,8,9]

我们发现使用交换法是比较笨的,发现一个数就进行交换效率并不高

希尔排序采用移动法

//对交换式的希尔排序进行优化->移位法
public static void shellSort2(int[] arr) {
    //增量gap,并逐步的缩小增量
    //进行分组数arr.length/2 每次都/2
    for(int gap=arr.length/2; gap>0; gap/=2){
        //从第gap个元素,逐个对其所在的组进行插入
        //int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        //i=5 对应的是arr[5] = 3
        for(int i=gap; i<arr.length; i++){
            //记录i的下标对应的值
            int j = i;//j=5
            int temp=arr[j];//arr[5]=3
            //arr[5]<arr[5-5] 若3<8
            if(arr[j]< arr[ j - gap]){
                //说明 j - gap 防止数组越界
                //说明 temp < arr[ j - gap]  若3 < 8
                while( j - gap >=0 && temp < arr[ j - gap] ){
                    //移动交换
                    arr[ j ]=arr[ j - gap ];//将arr[5]=arr[0] =8
                    j -= gap; //5-5=0
                }
                //当退出while循环后,即找到插入的位置
                arr[j]=temp;//arr[0]=3
            }
        }
    }
}

三、交换法与移动法的排序速度对比

int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("使用交换法排序前的时间是=" + date1Str);
shellSort(arr);
Date data2 = new Date( );
String date2Str = simpleDateFormat.format(data2);
System.out.println("使用交换法排序完的时间是=" + date2Str);
System.out.println("使用交换法排序完的数据是=" + Arrays.toString(arr));

//运行结果如下:
//使用交换法排序前的时间是=2020-04-12 09:57:30
//使用交换法排序完的时间是=2020-04-12 09:57:30
//使用交换法排序完的数据是=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Date data3 = new Date();
String date3Str = simpleDateFormat.format(data3);
System.out.println("使用移动法排序前的时间是=" + date3Str);
shellSort2(arr);
Date data4 = new Date( );
String date4Str = simpleDateFormat.format(data4);
System.out.println("使用移动法排序完的时间是=" + date4Str);
System.out.println("使用移动法排序完的数据是=" + Arrays.toString(arr));

//运行结果如下:
//使用移动法排序前的时间是=2020-04-12 09:57:30
//使用移动法排序完的时间是=2020-04-12 09:57:30
//使用移动法排序完的数据是=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

原数组的数据两种方法时间还是不错的,都是1秒直接完成,我们将数据放大到80000对比两个方法的速度

int[] arr = new int[80000];
for(int i=0;i<80000;i++){
    arr[i] = (int) (Math. random() * 8000000); //生成一个[0, 8000000) 数
}
Date data1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(data1);
System.out.println("使用交换法排序前的时间是=" + date1Str);
shellSort(arr);
Date data2 = new Date( );
String date2Str = simpleDateFormat.format(data2);
System.out.println("使用交换法排序完的时间是=" + date2Str);
System.out.println("使用交换法排序完的数据是=" + Arrays.toString(arr));

运行结果如下:
使用交换法排序前的时间是=2020-04-12 10:10:35
使用交换法排序完的时间是=2020-04-12 10:10:50

Date data3 = new Date();
String date3Str = simpleDateFormat.format(data3);
System.out.println("使用移动法排序前的时间是=" + date3Str);
shellSort2(arr);
Date data4 = new Date( );
String date4Str = simpleDateFormat.format(data4);
System.out.println("使用移动法排序完的时间是=" + date4Str);
System.out.println("使用移动法排序完的数据是=" + Arrays.toString(arr));

运行结果如下:
使用移动法排序前的时间是=2020-04-12 10:10:50
使用移动法排序完的时间是=2020-04-12 10:10:50

这次进行对比发现希尔排序交换法是比移动法更快时间复杂度也少


28640
116 声望25 粉丝

心有多大,舞台就有多大