简介
之前介绍的几种排序算法,简单选择排序、直接插入排序,它们的时间复杂度都是 O ( n 2 ) O(n^2) O(n2)。实际上在早期很长一段时间内,普遍认为排序算法的效率不会高于 O ( n 2 ) O(n^2) O(n2),直到希尔排序算法的出现才打破了这一认知。希尔排序是第一批冲破二次时间界( O ( n 2 ) O(n^2) O(n2))的算法之一。它通过比较相距一定距离的元素来排序,各趟比较所使用的距离随算法的进行而减小,直到比较相邻元素为止。因此,希尔排序也称缩减增量排序。
希尔排序通过使用一个增量序列
h
1
h_1
h1,
h
2
h_2
h2,…,
h
t
h_t
ht,将待排序元素序列分为若干个子序列。然后对这些子序列进行排序,使其
a
[
i
]
<
=
a
[
i
+
h
k
]
a[i]<=a[i+h_k]
a[i]<=a[i+hk],此时所以相隔
h
k
h_k
hk的元素都被排序,称为序列是
h
k
h_k
hk排序的。如下图。
下面我们继续通过代码进行逐步讲解,完整代码见https://github.com/kfcyh/sort/tree/master/shellsort。
代码讲解
void ShellSort(SqList L)
{
int i, j;
int increment = L->length;
do
{
increment = increment / 3 + 1; //增量序列
for (i = increment + 1; i <= L->length; i++)
{
if (L->r[i] < L->r[i - increment]) //需将r[i]插入有序增量子表
{
L->r[0] = L->r[i]; //暂存元素
for (j = i - increment; j > 0 && L->r[0] < L->r[j]; j -= increment)
L->r[j + increment] = L->r[j]; //元素后移
L->r[j + increment] = L->r[0]; //插入元素至正确位置
}
}
} while (increment > 1);
}
由代码可知,希尔排序对增量子序列的排序采用插入排序的思想,下面我们通过对序列{9,1,5,8,3,7,4,6,2}进行排序来进行讲解。
-
首先是一个 w h i l e while while循环,中止条件是 i n c r e m e n t = 1 increment=1 increment=1。然后初始化增量 i n c r e m e n t = l e n g t h / 3 + 1 increment=length/3+1 increment=length/3+1。内部的外循环 f o r for for每次对增量为 i n c r e m e n t increment increment的子序列进行排序。 i i i从 i n c r e m e n t + 1 increment+1 increment+1开始,中止条件为 i > l e n g t h i>length i>length。第一次 w h i l e while while循环, i n c r e m e n t = 4 increment=4 increment=4。
-
第一次 f o r for for循环, i = 5 i=5 i=5, r [ i ] = 3 < r [ i − i n c r e m e n t ] = 9 r[i]=3<r[i-increment]=9 r[i]=3<r[i−increment]=9,交换元素。
-
循环继续, i = 6 i=6 i=6, r [ i ] = 7 > r [ i − i n c r e m e n t ] = 1 r[i]=7>r[i-increment]=1 r[i]=7>r[i−increment]=1,不交换元素。
-
循环继续, i = 7 i=7 i=7, r [ i ] = 4 < r [ i − i n c r e m e n t ] = 5 r[i]=4<r[i-increment]=5 r[i]=4<r[i−increment]=5,交换元素。
-
同上继续循环至, i = 9 i=9 i=9, r [ i ] = 2 < r [ i − i n c r e m e n t ] = 9 r[i]=2<r[i-increment]=9 r[i]=2<r[i−increment]=9,交换元素。注意此时还要继续比较 r [ 5 ] r[5] r[5]与 r [ 1 ] r[1] r[1], r [ 5 ] < r [ 1 ] r[5]<r[1] r[5]<r[1]交换。
-
第一轮 w h i l e while while后,序列如下所示。
-
由于此时 i n c r e m e n t = 4 > 1 increment=4>1 increment=4>1,继续 d o do do循环。 i n c r e m e n t = i n c r e m e n t / 3 + 1 = 2 increment=increment/3+1=2 increment=increment/3+1=2,同上过程, i i i从3到9。当 i = 3 、 4 i=3、4 i=3、4时,不用交换,当 i = 5 i=5 i=5时,交换。
-
当 i = 6 、 7 、 8 、 9 i=6、7、8、9 i=6、7、8、9时,不用交换。
-
由于此时 i n c r e m e n t = 2 > 1 increment=2>1 increment=2>1,继续 d o do do循环。 i n c r e m e n t = i n c r e m e n t / 3 + 1 = 1 increment=increment/3+1=1 increment=increment/3+1=1,此时为最后一轮 w h i l e while while循环,同上过程。
-
完成排序,结果如下图。
性能分析
在上述排序过程中,增量" i n c r e m e n t increment increment"的选取十分关键,它选取什么最好目前还是一个数学难题,不过大量的研究表明,当增量序列为 d l t a [ k ] = 2 t − k + 1 dlta[k]=2^{t-k+1} dlta[k]=2t−k+1时可以获得不错效果。总结其时间复杂度为 O ( n 3 / 2 ) O(n^{3/2}) O(n3/2),好于直接排序的 O ( n 2 ) O(n^{2}) O(n2),需要注意的是,增量序列的最后一个增量必须是1。而且希尔排序不是一个稳定的排序算法。