在生活和学习中,我们常遇到要对一组数据或一系列的对象进行一种操作,使其成为一组从大到小或从小到大的序列。这就是我们所熟悉的排序。排序(Sorting)是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个关键字有序的序列。在生活和其它领域上也有广泛的应用。最早提出排序的概念的是美国的Herman Hollerith。当时主要是为了解决打孔机的问题,一直发展到现在,已经出现了许多类型的排序,如:插入排序,基数排序等等。本文主要讨论插入排序。
1、直接插入排序(Straight Insertion Sort)。这是一种最简单的排序方法,此排序方法是对于一个已经排好序的序列,在添加一个记录时,从后向前扫描,直到找到相应的位置后,将该记录插入到此有序序列中,从而得到一个新的有序的有序表。在查找中我们学过监视哨的作用,在这个插入排序的过程中我们也可以加以利用,这使得该算法需要一个额外的空间,即空间复杂度为O(1)。具体实现步骤如下(此过程考虑序列从小到大):
(1)、建立原有序表,且此有序表已经有序,第一个元素不保存数据,用来作为监视哨的作用。用r[0]表示该监视哨;
(2)、将待插入数据保存在r[0]中;
(3)、从原有序表的最后一项开始向前扫描,直到找到一项的数据小于或等于“哨兵”,记录下该位置为i;
(4)、将该位置后的所有元素后移,然后将监视哨覆盖到空出来的位置上,即r[i],且表长加1。
下面是建一个有序表的直接插入排序的算法实现:
void InsertSort(SqList &L) {
for(i = 2; i < L.length; ++i) {//从第二个开始
if(LT(L.r[i].key, L.r[i-1].key) {
L.r[0] = L.r[i];//如果表中记录比待插入记录小,则将其复制到哨兵的位置
L.r[i] = L.r[i-1];//先将最后一个记录后移,以便接下来的元素可覆盖其后面的位置
for(j = i - 2; LT(L.r[0].key, L.r[i-1]; --j)
L.r[j+1] = L.r[0];//记录后移
L.r[j+1] = L.r[0];//将哨兵覆盖到待插入位置上
}
}
}
2、希尔排序(Shell's Sort)。因为在直接插入排序的过程中,需要比较的次数很多,时间复杂度比较高。在最坏的情况下,需要进行(n2)次比较。所以希尔想到将比较的次数减少,即减少时间的复杂度。那么,怎样才能减少比较的次数呢?我们在直接插入排序的时候比较时是从最后一个开始一直到小于或等于待插入项。现在考虑一组待排的记录。利用直接插入排序时,需要从记录的第二个位置开始做插入排序。现在我们将记录分成一个个子序列,每个子序列中的两个数据的地址间隔都相等。然后对子序列进行插入排序,直到数据的地址间隔等于1时结束,此时序列已有序。这个称为缩小增量排序。缩小的增量是缩短两个元素之间的地址间隔。具体实现步骤如下:
(1)、将待排序列分成若干个子序列;
(2)、分别对每个子序列进行直接插入排序;
(3)、然后继续减小元素地址之间的间隔,返回第一步。
注:由上面描述可知希尔排序是一个递归过程。而且增量的取值是有规律的。每个增量除1以外不可以有公因子;最后一个增量必须是1。
下面是希尔排序的插入的算法实现:
void ShellInsert(SqList &L, int dk) {
for(i = dk + 1; i <= L.length; ++i) {
if(LT.r[i].key, L.r[i-dk].key)) {//将L.r[i]插入有序增量子表中
L.r[0] = L.r[i];//存在哨兵位置
for(j = i - dk; j > 0 && LT(L.r[0].key, L.r[j].key); j -= dk)
L.r[j+dk] = L.r[j];//记录后移,查找插入位置
L.r[j+dk] = L.r[0];//插入
}
}
}
下面是希尔排序的算法:
void ShellSort(SqList &L, int delta[], int t) {
for(k = 0; k < t; ++k)
ShellInsert(L, delta[k]);//增量为delta[k]
}