一、问题描述
内部排序是一件具有重大意义的问题,许多项目的实现中都需要用到排序。
我们知道,排序的算法有许多种,每种排序算法的时间复杂度和空间复杂度不尽相同。在解决实际问题时,往往需要根据实际需要选择排序算法。
本实验重点介绍希尔排序的算法实现及其原理,简要说明与其相关的直接排序算法,并讨论希尔排序中步长的选择对排序速度的影响。
二、数据结构——顺序结构
本实验重点在算法实现上,数据结构的思想被弱化了。在排序过程中,由于维护序关系的需要,要有交换的操作,这就破坏了ADT的物理位置的相邻反映逻辑的依次的性质,可以说这里的顺序结构只是一个二次结构。因此,本实验不对此作过多说明。
三、算法的设计和实现
(1)直接排序(直接插入排序)
a)简述
以不降序排序为例,直接排序在每一轮排序中,找到出现的第一个破坏不降序性质的元素,将其值保存至a[0]处,然后向前扫,直到找到小于或等于a[0]的元素a[i],然后将a[0]插入到a[i]之后,后面的元素依次后移一位。重复上述过程,由循环不变式可知,最后得到的序列是保持不降序性质的。
可以看出,直接排序是一种基于插入的排序算法。算法的时间复杂度为O(n^2)。值得注意的是,直接排序只涉及到两个相邻数的交换,所以是稳定的。
b)例子:空白处的表示不改变,当前循环将不会访问到。
a[0] | 49 | 38 | 65 | 97 | 76 | 13 | 27 |
38 | 38 | 49 | |||||
76 | 38 | 49 | 65 | 76 | 97 | ||
13 | 13 | 38 | 49 | 65 | 76 | 97 | |
27 | 13 | 27 | 38 | 49 | 65 | 76 | 97 |
(1)希尔排序
a)简述
希尔排序是直接排序的一个升级版。以步长将待排序的序列划分成几个部分,将每个部分进行排序,使得整个序列粗有序,从而减少序列的移动次数,减小时间复杂度。
b)算法描述
首先枚举步长gap,以步长将待排序的序列分为gap个子序列,对每个子序列进行直接排序。步长从粗到细,最后一次步长为1。步长的选取直接关系到希尔排序的时间效率,一般来说,为了尽量避免重复计算,选取的步长要互质;至于更系统的讨论将在下面给出。
c)例子
步长 | 49 | 38 | 65 | 97 | 76 | 13 | 27 | 49 | 55 | 4 |
5 | 13 | 27 | 49 | 55 | 4 | 49 | 38 | 65 | 97 | 76 |
3 | 13 | 4 | 49 | 38 | 27 | 49 | 55 | 65 | 97 | 76 |
1 | 4 | 13 | 27 | 38 | 49 | 49 | 55 | 65 | 76 | 97 |
d)可以看出,希尔排序不仅基于插入,还有大跨度交换的操作,所以希尔排序是不稳定的。
四、预期结果和实验中的问题
1、预期结果
程序能够正确地将一个序列按照不递减的顺序排序。下图为一个例子。
2、实验中的问题及思考
(1)问题:希尔排序的步长选择对算法效率的影响。
(2)解答:这是一个公开问题,似乎还没有一个特别完美的解释。维基百科上有相关的说明。https://en.wikipedia.org/wiki/Shellsort
附:c++源代码:
1 /* 2 项目:shell sort 3 作者:张译尹 4 */ 5 #include <iostream> 6 #include <cstdio> 7 #include <cstring> 8 9 using namespace std; 10 #define MaxN 120 11 12 int gap[200]; //步长 2^k+1 第一项改为1 13 int n; 14 15 template <class T> class My_list 16 { 17 private: 18 T Elem[MaxN]; //待排序的元素 19 int Len; //元素个数 20 public: 21 void Init() 22 { 23 memset(Elem, 0, sizeof(Elem)); 24 Len = 0; 25 } 26 void Insert_back(T x) 27 { 28 Elem[++Len] = x; 29 } 30 void Print() 31 { 32 int i; 33 for(i = 1; i < Len; i++) 34 printf("%d ", Elem[i]); 35 printf("%d ", Elem[i]); 36 } 37 int GetLen() 38 { 39 return Len; 40 } 41 void ShellSort() 42 { 43 int i, j, k; 44 for(k = gap[0]; k >= 1; k--) 45 { 46 for(i = gap[k]; i <= Len; i++) 47 { 48 Elem[0] = Elem[i]; 49 50 //把比当前元素大的都往后移动 51 for(j = i - gap[k]; j >= 0 && Elem[0] < Elem[j]; j -= gap[k]) 52 Elem[j + gap[k]] = Elem[j]; 53 Elem[j + gap[k]] = Elem[0]; 54 } 55 } 56 } 57 }; 58 59 void Read(My_list <int> &L) 60 { 61 int i, x; 62 L.Init(); 63 printf("请输入需要排序的数的个数。 "); 64 scanf("%d", &n); 65 printf("请输入需要排序的数列。 "); 66 for(i = 1; i <= n; i++) 67 { 68 scanf("%d", &x); 69 L.Insert_back(x); //把x插入到最后 70 } 71 } 72 73 void Pre_gap(My_list <int> &L) 74 { 75 int i, tmp = 2, n = L.GetLen(); 76 for(i = 2; ; i++) 77 { 78 tmp = ((tmp - 1) << 1) + 1; 79 gap[i] = tmp; 80 if(tmp >= n / 2) 81 break; 82 } 83 gap[1] = 1; 84 gap[0] = i; 85 } 86 87 int main() 88 { 89 int GapNum; 90 My_list <int> L; 91 Read(L); 92 Pre_gap(L); 93 L.ShellSort(); 94 printf("升序排序后的数列: "); 95 L.Print(); 96 return 0; 97 }