原理:
这个是基于插入排序的改进。将待排序的记录数目减少,所以,我们需要采用跳跃分割策略:将相距某个分量的记录组成一个子序列分别进行插入排序得到的结果是基本有序。
算法讲解:
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]r[i-increment]) {
// 需要将L->r[i]插入有序增量子表
L->r[0] = L->r[i];
for(j = i - increment;j>0&&L->r[0]r[j];j-=increment)
L->r[j+increment] = L->r[j]; // 记录后移,查找插入位置
L->r[j+increment] = L->r[0];
}
}
} while (increment>1);
}
1.程序开始,此时假设我们传入的SqList 参数值为 length = 9,r[10] = {0,9,1,5,8,3,7,4,6,2}.请看图
2.第四行,变量increment,初始值让它等于待排序的记录,9。
3.第5-19行是一个do循环,它终止条件是 increment 不大于 1 时。其实也是增量为1就停止循环。
4.第 7 行,这一句很关键,但也是难以理解的地方,我们后面还要谈到它1,先放一放。这里执行后,increment = 9 / 3 + 1 = 4。
5.第 8-17 行是一个 for 循环,i 从 4 + 1 = 5 开始到 9 结束。
6.第 10 行,判断 L.r[i] 与 L.[i-increment]大小,L.r[5] = 3小于L.r[i-increment] = r[1] = 9,第 12 行,将L.r[5] = 3 赞存入L.r[0]。第 13-14 行的循环只是为了将L.r[1] = 9的值赋值给L.r[5],由于循环的增量是 j -= increment,其实它就循环一次,此时 j = -3. 第 15 行,再将 L.r[0] = 3 赋值给 L.r[j+increment] = L.r[1] = 3.如图,事实上,这一段代码就干了一件事,就是将第 5 位的 3 和 第 1 位的 9 交换了位置。
7.循环继续,i = 6,L.r[6] = 7 > L.r[i - increment] = L.r[2] = 1,因此不交换两组数据。如图
8.循环继续,i = 7,L.r[7] = 4 < L.r[i-increment] = L.r[3] = 5,交换两者数据。如图
9.循环继续, i = 8,L.r[8] = 6 < L.r[i-increment] = L.r[4] = 8,交换两者数据。如图。
10.循环继续,i = 9,L.r[9] = 2 < L.r[i-increment] = L.r[5] = 9,两者交换数据。注意,第 13 和 14 行是循环,此时还要继续比较 L.r[5] 与 L.r[1] 的大小,因为 2 < 3,交换位置。
最终第一轮循环后,数组的结果如下图所示。我们的数字1,2等小数字基本已经在前两位,而8,9等大数字都基本在最后,也就是说,通过这样的排序,我们的整个序列基本有序。这就是希尔排序的精华所在,不是一步一步移动,而是跳跃式的前移,从而使没次完成一轮循环后,整个序列就朝着有序坚实地迈进一步。
11.我们继续,在完成一轮do循环后,此时由于increment = 4 > 1因此我们需要继续 do 循环。第 7 行 increment = 4 / 3 + 1 = 2。第 8 - 17 行 for 循环,i 从 2 + 1= 3 开始到 9 结束。当i = 3,4时,不用交换,当 i = 5 时,需要交换。
12. 此后, i = 6,7,8,9均不用交换。
13.再完成一轮do循环,increment = 2,继续do循环,第七行得到 increment = 1,最后一次循环了。尽管 8-17 行 for 循环,i 从 2 到 9结束,但由于当前的序列更接近有序,可交换的数据更少了。最终完成排序。
复杂度分析:
这里“增量”的选取就非常关键了。我们用 increment = increment / 3 + 1;的方式选取增量,可究竟应该选取什么样的增量才是最好,目前还是一个数学难题,迄今还没有人找到一种最好的增量序列。不过大量研究表明,当增量序列为 时,可以获得不错的效率,其时间复杂度为 要好于直接排序 O(n^2).需要注意的是,增量序列最后一个增量值必须等于 1 才行。另外,由于记录是跳跃式的移动,希尔排序并不是一个稳定的排序算法。