常见排序算法:希尔排序

希尔排序(Shell's sort)是一种非常“神奇”的排序算法。说它“神奇”,是因为没有任何人能清楚地说明它的性能到底能到什么情况。希尔排序因DL.Shell于1959年提出而得名。自从C. A. R. Hoare在1962年提出快速排序后,由于其更为简单,一般采用快速排序。但是,不少数学家们还是孜孜不倦地寻找希尔排序的最佳复杂度。作为普通程序员,我们可以学习下希尔的思路。

顺便说一句,在希尔排序出现之前,计算机界普遍存在“排序算法不可能突破O(n2)”的观点。希尔排序的出现打破了这个魔咒,很快,快速排序等算法相继问世。从这个意义上说,希尔排序带领我们走向了一个新的时代。

 

算法概述/思路

希尔排序的提出,主要基于以下两点:

1.插入排序算法在数组基本有序的情况下,可以近似达到O(n)复杂度,效率极高。

2.但插入排序每次只能将数据移动一位,在数组较大且基本无序的情况下性能会迅速恶化。

 

基于此,我们可以使用一种分组的插入排序方法,具体做法是:(以一个16元素大小的数组为例)

1.选择一个增量delta,该增量大于1,从数组中按此增量选择出子数组进行一次直接插入排序。例如,若选择增量为5,则对下标为0,5,10,15的元素进行排序。

2.保留该增量delta并依次移动首个元素进行直接插入排序,直到一轮完成。对于上面的例子,则依次对数组[1,6,11],[2,7,12],[3,8,13],[4,9,14]进行排序。

3.减小增量,不断重复上述过程,直到增量减小为1.显然,最后一次为直接插入排序。

4.排序完成。

从上面可以看出,增量是不断减小的,因此,希尔排序又被成为“缩小增量排序”。

下面是希尔排序的示意图(图片来自维基百科):

 http://upload.wikimedia.org/wikipedia/commons/d/d8/Sorting_shellsort_anim.gif


代码实现

实现代码1:

public static void shellSort(int[] arr){ 
    int temp; 
    for (int delta = arr.length/2; delta>=1; delta/=2){                              //对每个增量进行一次排序 
        for (int i=delta; i<arr.length; i++){               
            for (int j=i; j>=delta && arr[j]<arr[j-delta]; j-=delta){ //注意每个地方增量和差值都是delta 
                temp = arr[j-delta]; 
                arr[j-delta] = arr[j]; 
                arr[j] = temp; 
            } 
        }//loop i 
    }//loop delta 
}

实现代码2:

public static void shellSort2(int[] arr){ 
    int delta = 1; 
    while (delta < arr.length/3){//generate delta 
        delta=delta*3+1;    // <O(n^(3/2)) by Knuth,1973>: 1, 4, 13, 40, 121, ... 
    }          
    int temp; 
    for (; delta>=1; delta/=3){ 
        for (int i=delta; i<arr.length; i++){               
            for (int j=i; j>=delta && arr[j]<arr[j-delta]; j-=delta){ 
                temp = arr[j-delta]; 
                arr[j-delta] = arr[j]; 
                arr[j] = temp; 
            } 
        }//loop i 
    }//loop delta 
}

算法性能/复杂度

希尔排序的增量数列可以任取,需要的唯一条件是最后一个一定为1(因为要保证按1有序)。但是,不同的数列选取会对算法的性能造成极大的影响。上面的代码演示了两种增量。

切记:增量序列中每两个元素最好不要出现1以外的公因子!(很显然,按4有序的数列再去按2排序意义并不大)。

下面是一些常见的增量序列。

第一种增量是最初Donald Shell提出的增量,即折半降低直到1。据研究,使用希尔增量,其时间复杂度还是O(n2)。

第二种增量Hibbard:{1, 3, ..., 2^k-1}。该增量序列的时间复杂度大约是O(n^1.5)。

第三种增量Sedgewick增量:(1, 5, 19, 41, 109,...),其生成序列或者是9*4^i - 9*2^i + 1或者是4^i - 3*2^i + 1。

下面的表中有更多的增量(来自http://en.wikipedia.org/wiki/Shellsort):

常见排序算法:希尔排序_第1张图片

算法稳定性

我们都知道插入排序是稳定算法。但是,Shell排序是一个多次插入的过程。在一次插入中我们能确保不移动相同元素的顺序,但在多次的插入中,相同元素完全有可能在不同的插入轮次被移动,最后稳定性被破坏,因此,Shell排序不是一个稳定的算法。

 

算法适用场景

Shell排序虽然快,但是毕竟是插入排序,其数量级并没有后起之秀--快速排序O(n㏒n)快。在大量数据面前,Shell排序不是一个好的算法。但是,中小型规模的数据完全可以使用它。


你可能感兴趣的:(java,希尔排序)