一、基本思想
希尔排序是希尔提出的,是通过引入增量,对简单插入排序的改进的高效版本,也叫缩小增量排序。
希尔排序是把数据按照下标的一定增量分组,对每组数据进行直接插入排序算法进行排序。随着增量主键减小,每一组包含的数据越来越多,当增量减至1时,所有数据恰被分成一组,算法进行最后一轮排序,使所有数据顺序排列。
希尔排序示意图:
二、代码实现
此处以从小到大排序为例,用两种方式实现:
1)当发现待插入的数比同组数据大时,直接交换位置;
2)当发现待插入的数比同组数据大时,将较小的数向后移动,直到找到合适位置时插入。
package sort;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
* 希尔排序
* 1)对插入排序的优化,改善了插入排序将最小(大)的数移动到最前面,导致其他数移动次数很多的问题。
*
* 2)引入增量,将一组数按照增量分组,并将每一组的数进行插入排序,然后在缩小增量,直到增量为1,对数组做最后一次插入排序,令整个数组有序排列为止。
* 增量的计算:
* 3)第一次排序的增量为 数组长度除以2,取整。第二次增量为第一次增量除以2,取整。依此类推,直到增量为1,做最后一次排序(即:能得到几个大于零的增量,就循环几轮,循环条件是:增量>0)。
* 4)每一轮循环中,将数组按照增量分组(数组长度除以增量=分成的组的数量)并将每一组中的数进行插入排序。
* 分组逻辑:从第一个数开始,其下标值+增量,所得到的下标位置对应的数就是和它一组的数。
*
*/
public class ShellSort {
public static void main(String[] args) {
int[] arr = new int[80000];
for(int i = 0;i<arr.length;i++){
int value = (int)(Math.random()*10);
arr[i] = value;
}
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
System.out.println("排序前:"+ format.format(new Date() ));
// System.out.println(Arrays.toString(arr));
shellSort2(arr);
System.out.println("排序后:"+format.format(new Date() ));
// System.out.println(Arrays.toString(arr));
}
/**
* 交换的方式(从小到大)
* @param arr
*/
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++){ //找到增量值对应的下标所在位置的数,该数和第一个数属于同一组,接着往后循环,直到数组的最后一个数向前找到对应的同组数据。
for(int j = i-gap; j>=0; j-=gap){// j为和下标为gap的数对应的同一组数据
if(arr[j] > arr[j+gap]){
temp = arr[j+gap];
arr[j+gap] = arr[j];
arr[j] = temp;
}
}
}
// System.out.println("希尔排序第"+(++count)+"轮排序结果:");
// System.out.println(Arrays.toString(arr));
}
}
/**
* 没找到可插入的位置时,向后移动数值,找到位置时插入
* @param arr
*/
public static void shellSort2(int[] arr){
int count = 0;
for(int gap=arr.length/2 ; gap>0; gap/=2){
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]){// 此处比较的是 temp 和 arr[j-gap]的大小,如果temp>=arr[j-gap] 或者将数组遍历完,说明j-gap上的数比temp小了,此时可插入的位置就是下标为j的位置
arr[j]=arr[j-gap];
j -= gap; //按照增量向前找下一个同组数据
}
arr[j] = temp;
}
}
// System.out.println("希尔排序第"+(++count)+"轮排序结果:");
// System.out.println(Arrays.toString(arr));
}
}
}
三、性能对比
1、交换位置的方式:80000个数,大概需要6S左右,而直接插入排序耗时大概500ms左右,可以看出,交换位置的方式反而更慢了。
2、向后移动位置的方式:
80000个数,大概需要17ms左右,而直接插入排序耗时大概500ms左右,可以看出,向后移动位置的方式在效率上比直接插入排序有较高的改善。