希尔排序算法基础

Posted 一只快活的野指针

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了希尔排序算法基础相关的知识,希望对你有一定的参考价值。

基本思想:

     希尔排序按其发现者希尔(Donald Shell)的名字命名,它是一种基于插入排序的快速排序算法,要了解希尔排序,必须先掌握插入排序的原理与实现。

    希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的间隔进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。这里涉及一个间隔序列的问题。

        在间隔大的时候,希尔排序移动的次数较少,在间隔小的时候,希尔排序移动的距离小,所希尔排序要比普通插入排序效率高。但是由于希尔排序是跳着排,所以它不稳定。

      综上所述,希尔排序总体思想就是先将整体无序数组变成局部有序数组,在其间将排序间隔缩短直到为1的时候变成局部有序的插入排序算法。

时间复杂度
空间复杂度
稳定性O

O(n1.3 )

O(1)
不稳定

       据说,希尔排序在工程上用得不多,不知道是不是因为不稳定的原因。听说,希尔排序在面试中考到并不多。另外,希尔排序的时间复杂度计算起来非常复杂,就现教材上描述,现在还没有人精确的计算出希尔排序的时间复杂度,社会上普遍认为希尔排序最好的时间复杂度能达到O(n1.3)

第一次编程(以4为间隔进行的排序,整体无序数组变成局部有序数组):

public class Shell { public static void main(String[] args) { int[] arr={12,5,6,8,24,75,89,34,2,66}; sort(arr); System.out.println("以间隔为4进行插入排序的结果:"); print(arr); } static void sort(int arr[]){ int gap=4;//自定义,以间隔为4进行插入排序 for(int i=gap;i<arr.length;i++){ for(int j=i;j>gap-1;j-=gap){ if(arr[j]<arr[j-gap]) exchange(arr,j,j-gap); }
} } static void print(int arr[])//封装函打印函数 { for(int k=0;k<arr.length;k++) { System.out.print(arr[k]+" "); } }
static void exchange(int arr[],int i,int j)//封装交换函数 { int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp;  }}

运行结果:

以4为间隔进行的排序:

排序前 12,5,6,8,24,75,89,34,2,66

排序后 2 5 6 8 12 66 89 34 24 75

sort()函数的运行思想:

第一次轮抽出12,24,2用插入排序排好顺序:

排序前 12,5,6,8,24,75,89,34,2,66

排序后 2,5,6,8,12,75,89,34,24,66

第二次轮抽出5,75,66用插入排序排好顺序:

排序前2,5,6,8,12,75,89,34,24,66

排序后 2,5,6,8,12,66,89,34,24,75

第三次轮抽出6,89用插入排序排好顺序:

排序前 2,5,6,8,12,66,89,34,24,75

排序后 2,5,6,8,12,66,89,34,24,75

第四次轮抽出8,34用插入排序排好顺序:

排序前 2,5,6,8,12,66,89,34,24,75

排序后 2,5,6,8,12,66,89,34,24,75

到这里发现人工算的结果和机器算的一样,证明我算的没错。其实开始我算的结果和机器算的结果不一致,后来才发现我们程序有个地方写错了。把if(arr[j]<arr[j-gap])写成if(arr[j]<arr[j-1])。


第二次编程(采用间隔为4逐次成倍递减的方式进行排序,希尔排序功能实现)

public class Shell{ public static void main(String[] args) { int[] arr={12,5,6,8,24,75,89,34,2,66}; sort(arr); System.out.println("以最初间隔为4进行插入排序的结果:"); print(arr); } static void sort(int arr[]){ for(int gap=4;gap>0;gap /= 2){//每次间隔缩小一倍,该层循环控制间隔变化。此处起始间隔固定 for(int i=gap;i<arr.length;i++){//该层循环控制固定间隔进行排序 for(int j=i;j>gap-1;j-=gap){ if(arr[j]<arr[j-1]) exchange(arr,j,j-gap); }  } } }
static void print(int arr[])//封装函打印函数 { for(int k=0;k<arr.length;k++) { System.out.print(arr[k]+" "); } } static void exchange(int arr[],int i,int j)//封装交换函数 { int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; }}

运行结果:


第三次编程(以折半序列取间隔的希尔排序,升级版)

public class Shell{ public static void main(String[] args) { int[] arr={12,5,6,8,24,75,89,34,2,66}; sort(arr); System.out.println("最终排序的结果:"); print(arr); } static void sort(int arr[]){ //每次间隔缩小一倍,该层循环控制间隔变化。起初起始间隔为数组长度的一半,下次循环间隔为一半的一半。 for(int gap=arr.length/2;gap>0;gap /= 2){ for(int i=gap;i<arr.length;i++){//该层循环控制固定间隔进行排序 for(int j=i;j>gap-1;j-=gap){ if(arr[j]<arr[j-1]) exchange(arr,j,j-gap); }  } } }
static void print(int arr[])//封装函打印函数 { for(int k=0;k<arr.length;k++) { System.out.print(arr[k]+" "); } }
static void exchange(int arr[],int i,int j)//封装交换函数 { int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; }}

运行结果:同上


第四次编程(采用Knuth序列取间隔的希尔排序,这是最好的版本)

       Knuth序列需要是由美国计算机科学家Donald Ervin Knuth(高德纳)发明,他是图灵奖获得者,个人生平非常传奇。Knuth中文谐音读"niu",K不发音。

Knuth序列的思想:

h=1

h=3*h+1

 由上述希尔排序最小间隔序列必须为1,所以h=1就是从1开始,往大增的话就是h=3*h+1。这样按照Knuth序列的思想,第一个间隔为1,第二间隔为3*1+1=4,第三个间隔是3*4+1=13........然后依次类推。

      假设一个需要排序的数组特别长,现在我们按照Knuth序列的思想去取间隔,那么就得先确定取得的最大间隔。最大间隔要小于等于数组长度的三分之一。为什么呢,如果h超过数组长度的1/3,h*3+1就会超过数组本身长度,那么该间隔就没有任何意义了。实践已经证明,希尔排序用Knuth序列取间隔效率要比折半序列取间隔要高。还有很多其它的序列,比如以质数作为核心思想的序列等等。

public class Shell{ public static void main(String[] args) { int[] arr={12,5,6,8,24,75,89,34,2,66}; sort(arr); System.out.println("最终排序的结果:"); print(arr); } static void sort(int arr[]){ //Knuth序列:算出最大的间隔 int h=1; while(h <= arr.length/3){ h=h*3+1; } for(int gap=h;gap>0;gap =(gap-1)/3){//起初起始间隔为h,下次循环间隔为(h-1)/3 for(int i=gap;i<arr.length;i++){//该层循环控制固定间隔进行排序 for(int j=i;j>gap-1;j-=gap){ if(arr[j]<arr[j-1]) exchange(arr,j,j-gap); }  } } } static void print(int arr[])//封装函打印函数 { for(int k=0;k<arr.length;k++) { System.out.print(arr[k]+" "); } } static void exchange(int arr[],int i,int j)//封装交换函数 { int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; }}


最后,利用对数器程序判断我们写的希尔排序算法是否正确:

对数器程序具体思想在插入算法文章中有详述,这里就不多说了。

package Sort;import java.util.Arrays;import java.util.Random;/*对数器:利用系统产生多个随机样本,将系统排序的结果和自己写的程序输出结果进行比对,判断是否正确。 * */public class DataChecker { //利用Random随机生成1000个10000以内的任意整数,并输出一个数组 static int[] generateRandomArray(){ Random r=new Random(); int[] arr=new int[10000]; for(int i=0;i<arr.length;i++){ arr[i]=r.nextInt(10000); } return arr; //这时得到长度为10000的数组,里面装的时,随机产生的数
} static void check(){ int[] arr=generateRandomArray(); int[] arr2=new int[arr.length];//对随机数组arr进行拷贝 System.arraycopy(arr,0,arr2,0,arr.length );
Arrays.sort(arr);// 系统对arr数组进行排序 Shell.sort(arr2);//自己写的算法进行排序
boolean same=true;//对比自己算法与系统自带排序算法是否一致 for(int i=0;i<arr2.length;i++){ if(arr[i]!=arr2[i]) same=false; } System.out.print(same==true ? "right":"wrong"); }
public static void main(String[] args) { check(); }}

运行结果:

由对数器的运行结果可以知道,我们写的希尔排序算法是正确无误的。

OK,希尔排序终于写完了,之前很少接触到希尔排序,所以我也不会写。经过这次总结,我也大概理清了希尔排序的思想。下期可能更新归并排序,该排序会涉及到递归思想。

https://www.cnblogs.com/wildpointer/


ColorC1Troupe

万     物     皆     可     递     归



以上是关于希尔排序算法基础的主要内容,如果未能解决你的问题,请参考以下文章

基础排序算法三——希尔排序

基础排序算法总结(代码+图片分析)

希尔排序算法

希尔排序算法基础

排序算法入门之希尔排序(java实现)

基础算法|7 希尔排序 HDU 1425