美文网首页
希尔排序

希尔排序

作者: 己庚辛壬癸 | 来源:发表于2018-11-01 01:14 被阅读13次

    希尔排序一种是很常见的排序算法,该算法在1959年由Donald Shell公布。

    希尔排序的奥妙

    • 1、希尔排序的特殊之处是将待排序的数列看做是一个二维的矩阵,对序列的各列进行排序,我们将所有矩阵的w列的各自排序总称为w-sorting;同时我们将各列都排序完成的矩阵叫做w-ordered
    • 2、递减增量;希尔排序的过程中不断重排矩阵,使其宽度w更窄,重排后都要进行w-sorting操作;直到矩阵的宽度为1
    • 3、步长序列;在整个排序过程中使用到的宽度构成的逆序列叫做步长序列;步长序列要求首项为1,其余项递增;如:h = {w1 = 1, w2, w3, ..., wk}

    总结来说就是:假设初始矩阵的宽度为w;在得到该矩阵后对其每一列进行排序操作,排序完成后将矩阵的宽度w按照一定的序列缩小;缩小后又进行列排序;如此往返,直到宽度w=1,此时完成列排序后就得到了一个有序序列。

    ps: 步长序列有很多种的选择,每一种选择都对应着一种不同样的算法,它们有不同的性能,因此与其说希尔排序是一种算法,不如说它是一种排序框架。

    实例

    假设有待排序序列:99,20,15,30,19,07,10,33,42,82,55,02,78,66,77

    第一次取矩阵的宽度为 8,则可转化为如下:

    99,20,15,30,19,07,10,33
    42,82,55,02,78,66,77
    

    经过列排序后为:

    42,20,15,02,19,07,10,33
    99,82,55,30,78,66,77
    

    第二次取矩阵宽度为4,则可将已经进行列排序的上述矩阵转换成:

    42,20,15,02
    19,07,10,33
    99,82,55,30
    78,66,77
    

    再次对矩阵列排序:

    19,07,10,02
    42,20,15,30
    99,82,55,33
    78,66,77
    

    第三次取矩阵的宽度为3:

    19,07,10
    02,42,20
    15,30,99
    82,55,33
    78,66,77
    

    列排序后:

    02,07,10
    15,30,20
    19,42,33
    78,55,77
    82,66,99
    

    第4次取矩阵宽度为2:

    02,07
    10,15
    30,20
    19,42
    33,78
    55,77
    82,66
    99
    

    列排序后得到:

    02,07
    10,15
    19,20
    30,42
    33,66
    55,77
    82,78
    99
    

    最后w缩小到1:

    02
    07
    10
    15
    19
    20
    30
    42
    33
    66
    55
    77
    82
    78
    99
    

    再次进行列排序:

    02
    07
    10
    15
    19
    20
    30
    33
    42
    55
    66
    77
    78
    82
    99
    
    为何如此大费周章

    既然最后将矩阵压缩到宽度为1的时候还要进行一次列排序,那么为何不直接进行排序,而要如此大费周章?

    在回答这个问题之前,我们先看一个关于邮资的问题:在某国家寄一封普通的信需要50分,寄一个明信片需要35分;该国家有且仅有两种面额的邮票,4分邮票和13分邮票。请问能否恰好找到一种组合使得邮资分别与之对应呢?

    相信经过一番的计算之后能够找到恰好组成50分的算法(6个4分邮票和2个13分邮票),但是并不能找到组成35分的邮资算法。

    不难理解,所有能够组成的邮资都满足表达式:4m + 13n ;m,n ∈ N,这样的式子也叫做线性组合。

    推而广之,使用g,h分别代表两种邮票的面额,则可组成的邮资:

    f = mg + nh; m,n ∈ N(又叫做 g和h的线性组合)
    

    尽管如此,始终还是有些邮资无法组合出来,我们将这些邮资放入一个集合:

    N(g, h) = {不能有g,h线性组合成的邮资}
    

    关注其中的最大者,将其设为x,设x(g, h) = max(N(g, h))

    根据数论可以得到:x(g, h) = (g-1)*(h-1) - 1 = gh - (g + h)

    不妨将上述的两种邮票面额带入其中,得到 x(4, 13) = 4 x 13 - (4 + 13) = 35;恰好35分为不能组合成的邮资的最大值。也就是说大于35分的邮资都能够恰好由这两种面额的邮票组成。

    邮资问题与希尔排序的关系

    我们先引入h-sorting 和 h-ordered的概念:

    • h-ordered,在一个序列中,任何距离为h的元素都保持前小后大的次序,我们就称它为h-orderedS[i] ≤ S[i + h] ; 0 ≤ i < n - h
    • h-sorting,我们使用希尔排序的方式,将序列从逻辑上转换成有h列的二维矩阵,然后再进行列排序,这个过程就叫h-sorting

    定理K

    假设矩阵经过一次 g-sorting,然后进行一次 h-sorting;那么从g-ordered到h-ordered后,该矩阵是否还有着g-ordered的特性呢?

    答案是:具有g-ordered的特性。[Knuth,ACP Vol.3 p.90]

    证明过程大家可以百度了解。

    在回到线性组合,假设一个序列,同时满足g-orderedh-ordered,那么不难推出它也满足(g+h)-ordered;同理合理推而广之这个序列也将是满足(mg+nh)-ordered。简而言之:能够表示为线性组合即为顺序的。

    对一个同时满足g-orderedh-ordered的序列(g和h互质);考察其第i个元素,那么与其不能构成线性组合的最大元素步长差为:(g-1)(h-1);在大与此步长之后的元素与i都是顺序的;也就是说能与i产生逆序的元素在i到其后(g-1)(h-1)之间;随着希尔排序的进行,这个范围也在缩小,能够产生的逆序对也就逐渐变小。由此我们不难联想到输入敏感的插入排序

    内部排序算法的实现(列排序)

    列排序时,必须采用输入敏感的算法,以保证有序性可以持续改善,且总体的成本足够低廉

    对于输入敏感,我们很容易想到插入排序(insertion sort),当插入排序的输入序列有序时,它的时间复杂度就接近线性(O(n))。因此,插入排序可以是希尔排序的内部列排序的实现。

    步长序列的选择

    较早且常见的一种步长序列是希尔序列:

    H = {1,2,4,8,...,2^k,...}
    

    该序列并不是一个较好的序列,采用该序列进行希尔排序,最坏的情况下将需要O(n²)的运行时间。

    如:

    11 4 14 3 10 0 15 1 9 6 8 7 13 2 12 5
    

    上述序列在完成2-sorting后,必然为如下结果:

    8 0 9 1 10 2 11 3 12 4 13 5 14 6 15 7
    

    不难看出其中的逆序对为算数级数量级(1+2+3+...+7 + 8);因此对于1-sorting仍需要:1+2...+2^(n-1)时间。

    因此在使用希尔排序时,选择一个好的步长序列是至关重要的。

    C/C++实现代码
    void shellSort(int * nums, int count) {
        int w, hs[] = {8,4,2,1};  // hs is the step sequence.
        for (int i = 0; i < 4; i++) {
            w = hs[i];
            for (int j = 0; j < w; j++) {
                // insertion sort
                int row = 1, searchRow = 0, value = 0;
                while (j + row * w < count) {
                    value = nums[j + row * w];
                    searchRow = row - 1;
                    while (searchRow >= 0 && nums[j + searchRow * w] > value) {
                        nums[j + (searchRow + 1) * w] = nums[j + searchRow * w];
                        searchRow--;
                    }
                    nums[j + (searchRow + 1) * w] = value;
                    row++;
                }
            }
        }
    }
    

    注:本文总结自《数据结构(C++语言版)》第三版,清华大学出版社,邓俊辉编著

    相关文章

      网友评论

          本文标题:希尔排序

          本文链接:https://www.haomeiwen.com/subject/rqqtxqtx.html