简介:
希尔(Shell)排序法是D.L.Shell在1959年发明的一种排序法,是第⼀个突破O(n2)的排序算法,是简单插⼊排序的改进版,其排序算法类似于简单插入排序,但它可以减少数据搬移的次数。希尔排序⼜叫缩⼩增量排序。
排序原则:
将数据区分成特定间隔的几个小分块,以插入排序法排完区块内的数据后再逐渐减少间隔的距离。
排序方法:
希尔排序与直接插入排序非常相似,区别就在于直接插入排序是按照元素位置顺序进行依次判断,希尔排序是将间隔的元素进行插入,用一个实例来看:
例:
原数组元素:6 9 2 3 4 7 5 1
-
组中有8个元素,所以先设n=8,将其分为n/2=4个小区块,但是这几个小区块的元素并不是相连的,而是有间隔的,第一次分隔的四个小区块分别为(6, 4),
(9, 7),(2, 5),(3, 1) -
将每个区块中的两个元素进行直接插入排序,得到结果的四个新的区块为:
(4, 6),(7 ,9),(2, 5),(1, 3)
第一次分块并插入排序后的数组元素为:4 7 2 1 6 9 5 3
-
再次缩小区块为(n/2)/2=2块,第二次分隔后的两个小区块为:(4, 2, 6, 5)
(7, 1, 9, 3) -
将每个区块中的四个元素进行直接插入排序,得到结果的两个新的区块为:(2,4,5,6),(1, 3, 7, 9)
第二次分块并插入排序后的数组元素为:2 1 4 3 5 7 6 7
-
再次缩小区块为((n/2)/2)/2=1块,第三次分隔后的仅剩一个区块,为:
(2, 1, 4, 3, 5, 7, 6 ,9 ) -
将这个这个区块中的元素进行直接插入排序,得到的结果为:(1, 2, 3, 4, 5, 6, 7, 9)
第三次分块并插入排序后的数组元素为:1 2 3 4 5 6 7 9
可以看出,随着每次对各分块进行排序后数组越来越趋近于有序,根据直接插入排序越有序越快的特点,希尔排序相对直接插入排序较快。
代码:
第一种写法:
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr = {5,2,-6,8,-3,9,7,4,1,0,-5,3,6};
System.out.println("排序前:");
System.out.println(Arrays.toString(arr));
ShellSort(arr);
System.out.println("ShellSort-排序后:");
System.out.println(Arrays.toString(arr));
}
public static void ShellSort(int[] arr){
//设置分组,第一次为n/2组
int gap = arr.length/2;
while(gap>=1){
for(int i = gap;i<arr.length;i++){
int tmp = arr[i];
//各组已排序区间第一个元素位置为i-gap
int j = i-gap;
//arr[j]为已排序区间与待插入元素对比的元素,
//j-=gap:由于希尔排序是间隔进行对比,所以每个待对比元素位置之间间隔为gap
for(;j>=0;j-=gap){
//若已排序区间元素大于tmp,进行交换
if(arr[j]>tmp){
arr[j+gap] = arr[j];
}else{
break;
}
}
arr[j+gap] = tmp;
}
gap = gap/2;
}
}
}
第二种写法:
public static void ShellSort(int[] arr){
//设置分组,第一次为n/2组
int gap = arr.length/2;
while(gap>=1){
for(int i = gap;i<arr.length;i++){
int tmp = arr[i];
//各组已排序区间第一个元素位置为i-gap
int j = i-gap;
//arr[j]为已排序区间与待插入元素对比的元素,
//j-=gap:由于希尔排序是间隔进行对比,所以每个待对比元素位置之间间隔为gap
while(j>=0 && arr[j]>tmp){
arr[j+gap] = arr[j];
j=j-gap;
}
arr[j+gap] = tmp;
}
gap = gap/2;
}
}
结果展示:
与直接插入排序进行对比:
关于直接插入排序,我前面的一篇文章有专门总结和实例展示,用的数据和本文相同点击查看:排序——直接插入排序
可以把链接中的代码拿过来与本文的代码进行对比,可以发现很多相似之处:
直接插入排序:
public static void MyInsertSort(int[] arr){
for (int i = 1; i < arr.length; i++) {
int tmp = arr[i];
int j = i-1;
for (; j >=0; j--) {
if(tmp <arr[j]){
arr[j+1] = arr[j];
}else{
break;
}
}
arr[j+1] = tmp;
}
}
希尔排序与之不同之处:
public static void ShellSort(int[] arr){
int gap = arr.length/2; //@1:设置分组,第一次为n/2组
while(gap>=1){
for(int i = gap;i<arr.length;i++){ //@2:直接插入排序中为:int i= 1;
int tmp = arr[i];
int j = i-gap; //@3:直接插入排序中为:int j = i-1;
for(;j>=0;j-=gap){ //@3:直接插入排序中为:for(;j>=0;j--){}
if(arr[j]>tmp){
arr[j+gap] = arr[j]; //@4:直接插入排序中为:arr[j+1] = arr[j];
}else{
break;
}
}
arr[j+gap] = tmp; //@5:直接插入排序中为:arr[j+1] = tmp;
}
gap = gap/2; //@6:直接插入中与分组无关
}
}
可以看出:
希尔排序与直接插入排序相比,仅仅是多了一个给原数组大小每次除2分组方式和用
while(gap>=1)
来进行是否继续分组的判断,其余地方就是把直接插入排序中的+1,-1都改成+gap,-gap即可,因为直接插入排序是按照元素在数组中的顺序(即间隔为1)进行对比插入,希尔排序是以分组数(即间隔为gap)为间隔进行对比插入,仅此而已。理解了直接插入排序,那么希尔排序就很简单了。