希尔排序,也称递减增量排序算法,是插入排序的一种高速而稳定的改进版本。希尔排序是基于插入排序的以下两点性质而提出改进方法的:
l 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率,如果当数据是”5, 4, 3, 2, 1“的时候,此时将“无序块”中的记录插入到“有序块”时,每次插入都要移动位置,此时插入排序的效率及其低下。
l 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
步骤如图:
增量步长的取法:
第一次增量的取法为:d=count/2;
第二次增量的取法为:d=(count/2)/2;
最后一直到: d=1;
看上图观测的现象为:
d=3时:
将40跟50比,因50大,不交换。
将20跟30比,因30大,不交换。
将80跟60比,因60小,交换。
d=2时:
将40跟60比,不交换,拿60跟30比交换,
此时交换后的30又比前面的40小,又要将40和30交换。
将20跟50比,不交换,继续将50跟80比,不交换。
d=1时:
这时就是前面讲的插入排序了,不过此时的序列已经差不多有序了,
所以给插入排序带来了很大的性能提高。
实现代码:
public class ShellSort {
// / 希尔排序
public static void shellSort(int[] list) {
// 取增量
int step = list.length / 2;
while (step >= 1) {
// 无须序列
for (int i = step; i < list.length; i++) {
int temp = list[i];
int j;
// 有序序列
for (j = i - step; j >= 0 && temp < list[j]; j = j - step) {
list[j + step] = list[j];
}
list[j + step] = temp;
}
step = step / 2;
}
}
public static void main(String[] args) {
int[] array = { 2, 5, 1, 8, 9, 3 };
System.out.println("排序前:" + Arrays.toString(array));
shellSort(array);
System.out.println("排序后:" + Arrays.toString(array));
}
}
运行结果:
排序前:[2, 5, 1, 8, 9, 3]
排序后:[1, 2, 3, 5, 8, 9]
既然说“希尔排序”是“插入排序”的改进版,那么我们就要比一下,在1w个数字中,到底能快多少?
测试代码:
public static void testInsertVSShellSort() {
for (int i = 1; i <= 5; i++) {
System.out.println("第" + i + "次比较:");
int[] arrays = new int[20000];
for (int j = 0; j < 20000; j++) {
arrays[j] = new Random().nextInt(1000000);
}
int[] array2 = Arrays.copyOf(arrays, arrays.length);
long begin = new Date().getTime();
insertSort(arrays); //插入排序
System.out.println("插入排序耗费时间:" + (System.currentTimeMillis() - begin));
begin = new Date().getTime();
shellSort(array2);//希尔排序
System.out.println("希尔排序耗费时间:" + (System.currentTimeMillis() - begin));
}
}
测试结果:
运行结果:
第1次比较:
插入排序耗费时间:109
希尔排序耗费时间:14
第2次比较:
插入排序耗费时间:74
希尔排序耗费时间:4
第3次比较:
插入排序耗费时间:85
希尔排序耗费时间:4
第4次比较:
插入排序耗费时间:82
希尔排序耗费时间:3
第5次比较:
插入排序耗费时间:82
希尔排序耗费时间:3