参考:维基百科 以及 数据结构与算法分析——Java语言描述 (美)Mark Allen Weis等
希尔排序的时间复杂度根据步长序列的不同而不同,最差空间复杂度O(n),是不稳定的排序算法。
希尔排序的名称源于它的发明者Donald Shell。该算法是冲破二次时间屏障的第一批算法之一。它通过比较相距一定间隔的元素来工作,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。因此,希尔排序也称递减增量排序算法,它实际上是插入排序的一种高速而稳定的改进版本。
插入排序的效率在某些时候是很高的,比如,我们的记录本身就是基本有序的,我们只需要少量的插入操作,就可以完成整个记录集的排序工作,此时直接插入很高效。还有就是记录数比较少时,直接插入的优势也比较明显。可问题在于,两个条件本身就过于苛刻,现实中记录少或者基本有序都属于特殊情况。(http://www.cnblogs.com/cj723/archive/2011/04/19/2021613.html)
所以,希尔排序就充分利用了这两个性质了。它首先将要排序的数据分成若干个子记录(记录少),对它们分别用插入排序,使得整组数据达到“基本”有序的状态,最后对整组数据(基本有序)进行一次插入排序,这样大大地提高了效率。分割子记录采取的是跳跃分割的策略:将相距某个“增量”的记录组成一个子记录,这样才能保证在子记录内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
希尔排序使用一个增量序列 h1 ,h2 ,……,ht ,叫做增量序列。只要h1=1,任何增量序列都是可行的。Shell建议的增量序列是:ht=[N/2] hk=[hk+1/2] 。虽然这样取可以比O(n2)类的算法(插入排序)更好,但这样仍然有减少平均时间和最差时间的余地。可能希尔排序最重要的地方在于当用较小步长排序后,以前用的较大步长仍然是有序的。比如,如果一个数列以步长5进行了排序然后再以步长3进行排序,那么该数列不仅是以步长3有序,而且是以步长5有序。如果不是这样,那么算法在迭代过程中会打乱以前的顺序,那就不会以如此短的时间完成排序了。
Hibbard提出了一个形如2k-1的增量序列,使用这个序列的希尔排序的最坏情形运行时间为O(n3/2) 。Sedgewick提出了几种增量序列,其最坏运行时间为O(N4/3),对这些增量序列的平均运行时间猜测为O(N7/6)。在实践中这些增量序列的运行要比Hibbard的要好得多,其中最好的是序列{1,5,19,41,109,……},该序列中的项或者是9*4i - 9*2i +1 或者是 4i - 3* 2i + 1。
我们现在来用代码实现吧。 别人提出的那些序列效果要好,但是怎么用呢?提前把这个序列放入一个数组吗?我怎么知道要用几个数呢?为了简单起见,我还是用Shell提出的序列吧。就做一点改动,如果数是偶数的话,将它减1,据说,这样会好些。
public class Sort {
public static void main(String[] args) {
Sort sort= new Sort();
int[] a={0,56,89,3,9,0,1,4,28,655,48,478,58,2658,98,145,33,66652,14,14};
sort.shellSort(a);
for(int i=0;i<a.length;i++)
System.out.println(a[i]);
}
//希尔排序法
public void shellSort(int[] a){
for(int gap=a.length/2;gap>0;gap/=2)
{
if(gap%2==0)
gap=gap-1;
for(int i=gap;i<a.length;i++)
{
int tmp=a[i];
int j=i-gap;
for(;j>=0&&tmp<a[j] ;j-=gap)
a[j+gap]=a[j];
a[j+gap]=tmp;
}
}
}
}