概述
希尔排序是希尔(Donald Shell)于1959年提出的一种不稳定排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
首先我们知道直接插入排序的时间复杂度最低的时候应该是序列基本有序,待排序的记录个数较少时,效率较高。
基于这个基础理论,希尔排序的基本思想如下:
先将整个待排记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序。
技巧:子序列的构成不是简单地逐段分割,将相隔某个增量dk的记录组成一个子序列,让增量dk逐趟缩短(如8,4,2,1),直到dk=1为止。
优点:小元素跳跃式前移,最后一趟增量为1时,序列已基本有序,平均性能优于直接插入排序。
增量序列
希尔排序的执行时间依赖于增量序列,一个好的增量序列有如下共同特征:
① 最后一个增量必须为1。
② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
希尔增量序列:n/2,(n/2)/2,...,1
使用希尔增量时希尔排序的最坏情形运行时间为θ(N^2)。
Hibbard增量序列:1,3,7,…,2^k-1。这个增量的特点是增量没有公因子。
使用Hibbard增量的希尔排序的最坏情形运行时间为θ(N^(3/2))。
Sedgewick提出了几种增量序列,其最坏情形运行时间为O(N^(4/3))。其中最好的是序列{1,5,19,41,109,…},该序列中的项是9*4^i-9*2^i+1的形式或者4^i-3*2^i+1。这个增量序列在实践中还是最为人们称道的。
希尔排序的性能在事件中是完全可以接受的,即使是对于数以万计的N仍是如此。编程的简单特点使得它成为对适度地大量的输入数据经常选用的算法。
基本思想
我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2...1}称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。
算法实现
public class ShellSort {
private static void shellSort(int[] arr) {
int j;
int len = arr.length;
for (int val = len >> 1; val > 0; val >>= 1) {
for (int i = val; i < len; i++) {
int temp = arr[i];
for (j = i; j >= val && temp < arr[j - val]; j -= val) {
arr[j] = arr[j - val];
}
arr[j] = temp;
}
}
}
public static void sort(int[] array) {
int inner, outer;
int temp;
int h = 1;
while (h <= array.length / 3) {
h = h * 3 + 1; // 生成间隔序列(1,4,13,40,121...),比如长度10序列4,1
}
while (h > 0) {
for (outer = h; outer < array.length; outer++) {
temp = array[outer];
inner = outer;
// 以4为增量间隔,子排序区间为(0,4,8)(1,5,9)(2,6)(3,7)
while (inner > h - 1 && array[inner - h] >= temp) {
// inner > h - 1 <==> inner - h >= 0即下标>=0;
array[inner] = array[inner - h];
inner -= h;
}
array[inner] = temp;
}
h = (h - 1) / 3;
}
}
public static void main(String[] args) {
int [] array = {5,3,0,2,4,1,9, 7,8,6};
System.out.println("Before: " + Arrays.toString(array));
sort(array);
System.out.println("After: " + Arrays.toString(array));
}
}