zoukankan      html  css  js  c++  java
  • 常见排序算法导读(7)[希尔排序]

    希尔排序(Shell Sort)又叫做缩小增量排序(Diminishing-increment Sort),是由D.L.Shell在1959年提出来的,旨在对直接插入排序做出改进以得到更好的时间效率。

    希尔排序的基本思想

    设待排序对象序列有N个对象,首先取一个整数gap(<N)作为间隔,将全部对象分为gap个子序列,所有距离为gap的对象放在同一个子序列中,在每一个子序列中分别实施直接插入排序,然后缩小间隔gap,例如gap=[gap/2],重复上述的子序列划分和排序工作,直到最后取gap==1,将所有对象放在同一个序列中进行直接插入排序为止。

    希尔排序为什么速度较快?

    由于开始时gap的取值较大,每个子序列中的对象较少,排序速度较快;待到排序的后期,gap取值逐渐变小,子序列中的对象个数逐渐变多,但由于前面工作的基础,大多数对象已经基本有序,所以排序速度依然很快。

    典型的希尔排序看起来是这样子滴,图片来源戳这里

    在希尔排序中用到的gap序列,常见的有下面几种:

    Gap Sequences: // https://en.wikipedia.org/wiki/Shellsort#Gap_sequences
    
    1. Shell's original sequence: N/2, ..., 4, 2, 1 (repeatedly divide by 2)
    2. Hibbard's      increments: 2**k-1, ..., 15,  7, 3, 1
    3. Knuth's        increments: ..., 121,    40, 13, 4, 1
    4. Sedgewick's    increments: ..., 109,    41, 19, 5, 1

    下面以Shell先生的序列为例,介绍典型的希尔排序过程。

    1. 输入序列为: 35 33 42 10 14 19 27 44 初始的增量为4, 分组为 {35, 14}, {33, 19}, {42, 27}{10, 14}

    2. 对上面的每一个分组进行插入排序,得到新的序列为 14 19 27 10 35 33 42 44

     

    旧分组 动作 新分组
    {35, 14} 将14插入到35前面 {14, 35}
    {33, 19} 将19插入到33前面 {19, 33}
    {42, 27} 将27插入到42前面 {27, 42}
    {10, 44} 无需调整 {10, 44}

    3. 将增量缩小为2对序列 14 19 27 10 35 33 42 44 进行处理, 分组为 {14, 27, 35, 42}{19, 10, 33, 44}

    4.  对上面的每一个分组进行插入排序,得到新的序列为 14 10 27 19 35 33 42 44

    旧分组 动作 新分组
    {14, 27, 35, 42} 无需调整 {14, 27, 35, 42}
    {19, 10, 33, 44} 将10插入到19前面 {10, 19, 33, 44}

    5. 将增量缩小为1对序列 14 10 27 19 35 33 42 44 进行处理,显然分组为 {14,10, 27, 19, 35, 33, 42, 44}

    6. 将上面的分组进行插入排序,得到最终的序列为 10 14 19 27 33 35 42 44

     

    注意: 以上6步的1-3步对应的图片来自于文章(Data Structure and Algorithms - Shell Sort)但是源链接上从第4步开始存在着错误(本来讨论的gap序列为4 2 1, 结果画图时用的gap序列却是4 1)(真是令人郁闷啦Orz),故4-6步对应的图片为私人定制(感谢贤妻对我博客写作的支持和不辞辛劳地PS!)。 

    下面给出基于Shell先生的gap序列的C代码实现。

     1 void shellsort(int a[], size_t n)
     2 {
     3         int h = 1;
     4 
     5         while (h < n / 2)
     6                 h = 2 * h; // 1, 2, 4, ... [N/2]
     7 
     8         while (h >= 1) {
     9                 for (int i = h; i < n; i++) {
    10                         for (int j = i; j >= h && (a[j] < a[j-h]); j -= h) {
    11                                 exchange(a, j, j-h);
    12                         }
    13                 }
    14 
    15                 h /= 2;
    16         }
    17 }
    18 
    19 static void exchange(int a[], int i, int j)
    20 {
    21         int t = a[i];
    22         a[i]  = a[j];
    23         a[j]  = t;
    24 }

    完整的C代码如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 
     4 static void exchange(int a[], int i, int j);
     5 static void show(int a[], size_t n);
     6 
     7 void shellsort(int a[], size_t n)
     8 {
     9         int h = 1;
    10 
    11         while (h < n / 2)
    12                 h = 2 * h; // 1, 2, 4, ... [N/2]
    13 
    14         while (h >= 1) {
    15                 for (int i = h; i < n; i++) {
    16                         for (int j = i; j >= h && (a[j] < a[j-h]); j -= h) {
    17                                 exchange(a, j, j-h);
    18 
    19                                 printf("		"); show(a, n);
    20                                 printf("	<--exchanged(a[%d], a[%d])
    ", j, j-h);
    21                         }
    22                 }
    23 
    24                 printf("#h=%d		", h); show(a, n); printf("	DONE
    ");
    25 
    26                 h /= 2;
    27         }
    28 }
    29 
    30 static void show(int a[], size_t n)
    31 {
    32         for (int i = 0; i < n; i++)
    33                 printf("%-2d ", a[i]);
    34 }
    35 
    36 static void exchange(int a[], int i, int j)
    37 {
    38         int t = a[i];
    39         a[i]  = a[j];
    40         a[j]  = t;
    41 }
    42 
    43 int main(int argc, char *argv[])
    44 {
    45         if (argc < 2) {
    46                 fprintf(stderr, "Usage: %s <C1> [C2] ...
    ", argv[0]);
    47                 return -1;
    48         }
    49 
    50         argc--;
    51         argv++;
    52 
    53         int n = argc;
    54         int *a = (int *)malloc(sizeof(int) * n);
    55 #define VALIDATE(p) do { if (p == NULL) return -1; } while (0)
    56         VALIDATE(a);
    57 
    58         for (int i = 0; i < n; i++)
    59                 *(a+i) = atoi(argv[i]);
    60 
    61         printf("                ");
    62         for (int i = 0; i < n; i++)
    63                 printf("%-2d ", i);
    64         printf("
    ");
    65 
    66         printf("Before sorting: "); show(a, n); printf("
    ");
    67         shellsort(a, n);
    68         printf("After  sorting: "); show(a, n); printf("
    ");
    69 
    70         free(a); a = NULL;
    71         return 0;
    72 }

    o 编译并测试

    $ gcc -g -Wall -m32 -std=c99 -o shellsort shellsort.c
    
    $ ./shellsort   8  7  6  5  4  3  2  1
                    0  1  2  3  4  5  6  7
    Before sorting: 8  7  6  5  4  3  2  1
                    4  7  6  5  8  3  2  1          <--exchanged(a[4], a[0])
                    4  3  6  5  8  7  2  1          <--exchanged(a[5], a[1])
                    4  3  2  5  8  7  6  1          <--exchanged(a[6], a[2])
                    4  3  2  1  8  7  6  5          <--exchanged(a[7], a[3])
    #h=4            4  3  2  1  8  7  6  5          DONE
                    2  3  4  1  8  7  6  5          <--exchanged(a[2], a[0])
                    2  1  4  3  8  7  6  5          <--exchanged(a[3], a[1])
                    2  1  4  3  6  7  8  5          <--exchanged(a[6], a[4])
                    2  1  4  3  6  5  8  7          <--exchanged(a[7], a[5])
    #h=2            2  1  4  3  6  5  8  7          DONE
                    1  2  4  3  6  5  8  7          <--exchanged(a[1], a[0])
                    1  2  3  4  6  5  8  7          <--exchanged(a[3], a[2])
                    1  2  3  4  5  6  8  7          <--exchanged(a[5], a[4])
                    1  2  3  4  5  6  7  8          <--exchanged(a[7], a[6])
    #h=1            1  2  3  4  5  6  7  8          DONE
    After  sorting: 1  2  3  4  5  6  7  8
    
    $ ./shellsort   35 33 42 10 14 19 27 44
                    0  1  2  3  4  5  6  7
    Before sorting: 35 33 42 10 14 19 27 44
                    14 33 42 10 35 19 27 44         <--exchanged(a[4], a[0])
                    14 19 42 10 35 33 27 44         <--exchanged(a[5], a[1])
                    14 19 27 10 35 33 42 44         <--exchanged(a[6], a[2])
    #h=4            14 19 27 10 35 33 42 44         DONE
                    14 10 27 19 35 33 42 44         <--exchanged(a[3], a[1])
    #h=2            14 10 27 19 35 33 42 44         DONE
                    10 14 27 19 35 33 42 44         <--exchanged(a[1], a[0])
                    10 14 19 27 35 33 42 44         <--exchanged(a[3], a[2])
                    10 14 19 27 33 35 42 44         <--exchanged(a[5], a[4])
    #h=1            10 14 19 27 33 35 42 44         DONE
    After  sorting: 10 14 19 27 33 35 42 44

    参考资料:

    1. Comparison Sorting Algorithms (Very cool, 强烈推荐)
    2. ShellSort from wikipedia
    3. Computer Algorithms: Shell Sort

    使用Knuth's 的gap序列之C代码实现 (参考书籍: 《Algorithms》 Fourth Edition P259)

     1 void shellsort(int a[], size_t n)
     2 {
     3         int h = 1;
     4 
     5         while (h < n / 3)
     6                 h = 3 * h + 1;  // 1, 4, 13, 40, 121, 364, 1093, ...
     7 
     8         while (h >= 1) {        // h-sort the array
     9                 for (int i = h; i < n; i++) {
    10                         // Insert a[i] among a[i-h], a[i-2*h], a[i-3*h]... .
    11                         for (int j = i; j >= h && (a[j] < a[j-h]); j -= h) {
    12                                 exchange(a, j, j-h);
    13                         }
    14                 }
    15 
    16                 h /= 3;
    17         }
    18 }
    19 
    20 static void exchange(int a[], int i, int j)
    21 {
    22         int t = a[i];
    23         a[i]  = a[j];
    24         a[j]  = t;
    25 }

    小结:

    希尔排序是一种不稳定的排序方法。其空间复杂度为O(1),但时间复杂度不仅取决于gap,还取决于gap之间的数学性质,比如它们的公因子等。因此,对希尔排序的时间复杂度的分析很困难,在特定情况下可以准确地估算关键字的比较次数和对象移动次数,但是想要弄清关键字比较次数和对象移动次数与增量(gap)选择之间的依赖关系,并给出完整的数学分析,目前还没有人能够做到。在高德纳的书中,利用大量的实验统计资料得出,当N很大时,关键字平均比较次数和对象平均移动次数大约在n**1.25到1.6*N**1.25范围内,这是在利用直接插入排序作为子序列排序方法的情况下得到的。 高德纳的学生Robert Sedgewick也说了"透彻理解希尔排序的性能至今仍然是一项挑战"。

    下一节,将介绍一种强大的选择排序算法,那就是令人魂牵梦萦的堆排序(Heap Sort)。

  • 相关阅读:
    安全测试WEB安全渗透测试基础知识(三)
    二次开发的Selenium Demo版本
    服务端性能测试工具校验v1.2
    渗透H5棋牌游戏棋牌游戏开发
    安全测试WEB安全渗透测试基础知识(一)
    源码网址
    使用ScribeFire写网易博客 imsho@126的日志 网易博客
    ScribeFire:和firefox完美结合的博客离线编辑器 博客联盟
    如何设置让 Everything 在 Win7 下开机启动 小众软件
    流言终结者——C语言内存管理 michael的个人空间 开源中国社区
  • 原文地址:https://www.cnblogs.com/idorax/p/6579332.html
Copyright © 2011-2022 走看看