常见排序算法导读(7)[希尔排序]

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

希尔排序的基本思想

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

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

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

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

《常见排序算法导读(7)[希尔排序]》

在希尔排序中用到的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}

《常见排序算法导读(7)[希尔排序]》

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

 《常见排序算法导读(7)[希尔排序]》

旧分组动作新分组
{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}

《常见排序算法导读(7)[希尔排序]》

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

《常见排序算法导读(7)[希尔排序]》

旧分组动作新分组
{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}

《常见排序算法导读(7)[希尔排序]》

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

《常见排序算法导读(7)[希尔排序]》 

注意: 以上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("\t\t"); show(a, n);
20                                 printf("\t<--exchanged(a[%d], a[%d])\n", j, j-h);
21                         }
22                 }
23 
24                 printf("#h=%d\t\t", h); show(a, n); printf("\tDONE\n");
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] ...\n", 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("\n");
65 
66         printf("Before sorting: "); show(a, n); printf("\n");
67         shellsort(a, n);
68         printf("After  sorting: "); show(a, n); printf("\n");
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)。

    原文作者:veli
    原文地址: https://www.cnblogs.com/idorax/p/6579332.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞