希尔排序算法(ShellSort)由计算机科学家Donald L.Shell与1959年发现,该算法基于插入排序,但是增加了一个特殊的实现机制,使得希尔排序算法能大大地优于插入排序算法。依赖于这个特殊的实现机制,使得希尔排序对多达几千的数据项的排序来说性能良好,其时间复杂度为O(N(logN)^2)。
一、希尔排序和增量排序的比较
- 插入排序-复制的次数太多:再插入排序执行到一半时,标记符左边这些数据项是排过序的,而标记符右边的数据项都没有排过序,这个算法取出标记符指向的标记项,并且将其存储在临时变量中,接着,从刚刚删除的这个标记项的左边一项开始,将这个临时变量一一插入试探,知道插入的位置正好使得标记项左边全部有序。这样就带了个一个问题,就是需要多次的移动和复杂数据项,造成算法的时间复杂度很大(插入排序的时间复杂度为O(N^2))。
- n-增量排序:这个是基于插入排序的一种排序思想,即加大对比的距离,从而达到减少对比的数据项的效果,当n=1时就是普通的插入排序了,当n=2时,就是对0、2、4、6…项进行排序;n越大,需要排序的项就越少,当n=1时,大部分数据都已排好了顺序,从而达到优化插入排序时间的目的。
二、Knuth序列
这是希尔排序中常用的一种增序排列的序列,被称为Knuth序列
;
三、希尔排序的思想
在希尔排序中,首先要通过一个循环中使用Knuth序列的生成公式来计算出最初的间隔n。n值最初被赋值为1,然后应用公式n = 3*n + 1
生成序列1,4,13,40,121,364,等等。当间隔n大于数组大小时这个循环过程停止。对于一个含有1000个数据项的数组,序列的第七个数字1093就太大了,因此使用序列的第六个数字作为最大的间隔来开始这个排序过程,即从364-增序排列。然后,每完成一次排序过程的外部循环,都用公式n = (n - 1)/3
来倒推出下一个间隔。
四、希尔排序Java实现
public class ShellSort {
@Test
public void testHeapSort() {
int[] array = {151, 10, 15, 18, -1, -51, 1, 0, 10};
sort(array);
System.out.println(Arrays.toString(array));
}
public void sort(int[] nums) {
// 思想:首先安装knuth序列构造出步长h,当h大于等于1时,不断的进行步长插入排序即可
int n = nums.length;
// 构造初始步长h
int h = 1;
while (h < n / 3) {
h = 3 * h + 1;
}
// 当步长大于等于1时,进行步长插入排序
while (h >= 1) {
for (int i = h; i < n; i++) {
for (int j = i; j - h >= 0 && nums[j] < nums[j - h]; j -= h) {
swap(nums, j, j - h);
}
}
// 将步长缩小
h /= 3;
}
}
private void swap(int[] array, int k, int j) {
int temp = array[k];
array[k] = array[j];
array[j] = temp;
}
}
// [-51, -1, 0, 1, 10, 10, 15, 18, 151]