1、前面的二路插入排序是将已有序数据分成两部分,使得后续插入操作只在其中一部分内进行,这样,缩小范围后
的查找及移动次数都会减少。推而广之,如果能将所有数据分成更多组,则每组内的数据量更小,这样,查找及移动
次数会进一步减少。希尔排序就是基于这样一种思路来设计的排序算法。
2、希尔排序(Shell Sort,又称缩小增量法)是一种分组插入排序方法,因DL.Shell于1959年提出而得名,实质上是一
种分组插入方法。
3、排序思想
(1)先取一个正整数d1(d1
每个k(k=1,2,…d1),R[k],R[d1+k],R[2d1+k],…分在同一组中,在各组内进行直接插入排序。这样一次分组和排序过程
称为一趟希尔排序;
(2)新的增量d2
(3)重复(2),直至所取的增量di等于1为止,即所有记录放进一个组中排序为止。
4、通常会以一组数据{49, 38, 65, 97, 76, 13, 27, 49, 55, 04}为例辅助说明,本文也以此为例:
(1)按照上述排序思想,需要先确定第一个增量,这里选择d1等于5,然后根据增量5将待排序数据分组如下:
原始待排序数据 | 49 | 38 | 65 | 97 | 76 | 13 | 27 | 49 | 55 | 04 |
---|---|---|---|---|---|---|---|---|---|---|
第1组分组 | 49 | 13 | ||||||||
第2组分组 | 38 | 27 | ||||||||
第3组分组 | 65 | 49 | ||||||||
第4组分组 | 97 | 55 | ||||||||
第5组分组 | 76 | 04 |
原始待排序数据 | 49 | 38 | 65 | 97 | 76 | 13 | 27 | 49 | 55 | 04 |
---|---|---|---|---|---|---|---|---|---|---|
第1组排序结果 | 13 | 49 | ||||||||
第2组排序结果 | 27 | 38 | ||||||||
第3组排序结果 | 49 | 65 | ||||||||
第4组排序结果 | 55 | 97 | ||||||||
第5组排序结果 | 04 | 76 |
(4)重新选择一个增量3(3小于5),对第一趟希尔排序的结果重新分组:
第1趟排序结果 | 13 | 27 | 49 | 55 | 04 | 49 | 38 | 65 | 97 | 76 |
---|---|---|---|---|---|---|---|---|---|---|
第1组分组 | 13 | 55 | 38 | 76 | ||||||
第2组分组 | 27 | 04 | 65 | |||||||
第3组分组 | 49 | 49 | 97 |
(5)对3个分组分别进行排序,结果如下:
第1趟排序结果 | 13 | 27 | 49 | 55 | 04 | 49 | 38 | 65 | 97 | 76 |
---|---|---|---|---|---|---|---|---|---|---|
第1组排序结果 | 13 | 38 | 55 | 76 | ||||||
第2组排序结果 | 04 | 27 | 65 | |||||||
第3组排序结果 | 49 | 49 | 97 |
(7)最后取增量为1,进行最后一次直接插入排序,因为此时序列已经“基本有序”,只需作少量的比较和移动即可完成排序。
5、算法示例(传入参数为顺序表的指针,建议用引用),另注R[0]不是哨兵:
(1)首先是一趟希尔排序的实现函数(三种写法):
//对顺序表L进行一趟希尔排序, 增量为dk
void
ShellInsert1(SqList* L,
int
dk)
{
int
i;
int
j;
// 对顺序表L作一趟希尔插入排序
for
(i = dk + 1; i <= L->Length; ++i)
{
if
(L->R[i].key < L->R[i - dk].key)
{
L->R[0] = L->R[i];
// 暂存数据
for
(j = i - dk; j > 0 && L->R[0].key < L->R[j].key; j -= dk)
{
L->R[j + dk] = L->R[j];
// 记录后移,查找插入位置
}
L->R[j + dk] = L->R[0];
// 插入
}
}
}
|
//对顺序表L进行一趟希尔排序, 增量为dk
void
ShellInsert2(SqList* L,
int
dk)
{
int
j;
// 从每一组的第二个开始,实现组内插入排序
for
(
int
i = dk + 1; i <= L->Length; i++)
{
L->R[0] = L->R[i];
//将待插入的i号元素暂存入0号元素以防后移有序元素时覆盖丢失
j = i - dk;
while
((j > 0) && (L->R[0].key < L->R[j].key))
{
L->R[j + dk] = L->R[j];
j = j - dk;
}
L->R[j + dk] = L->R[0];
}
}
|
//对顺序表L进行一趟希尔排序, 增量为dk
void
ShellInsert3(SqList* L,
int
dk)
{
int
i;
int
j;
// 对顺序表L作一趟希尔插入排序
for
(i = dk + 1; i <= L->Length; ++i)
{
if
(L->R[i].key < L->R[i - dk].key)
{
L->R[0] = L->R[i];
// 暂存数据
j = i - dk;
do
{
L->R[j + dk] = L->R[j];
j = j - dk;
}
while
((j > 0) && (L->R[0].key < L->R[j].key));
L->R[j + dk] = L->R[0];
// 插入
}
}
}
|
(2)按照增量调用上述函数即可实现希尔排序:
// 根据增量数组dk进行希尔排序,按增量序列dk[0 … t-1],对顺序表L进行希尔排序
void
ShellSort(SqList* L,
int
dk[],
int
t)
{
for
(
int
i = 0; i < t; i++)
{
// 任选一个插入函数,完成一趟排序
ShellInsert1(L, dk[i]);
//ShellInsert2(L, dk[i]);
//ShellInsert3(L, dk[i]);
}
}
|
6、附上测试用例的调用代码:
int
_tmain(
int
argc, _TCHAR* argv[])
{
SqList L;
// 初始化数据
L.Length = 10;
int
nNum[11] = {0, 49, 38, 65, 97, 76, 13, 27, 49, 55, 04};
for
(
int
i = 0; i <= L.Length; i++)
{
L.R[i].key = nNum[i];
}
// 按当前顺序显示
Fun::PrintNowOrder(&L);
// 增量数组
int
dk[10] = {5, 3, 1};
int
t = 3;
// 进行希尔排序
ShellSort(&L, dk, t);
// 排序结果
Fun::PrintNowOrder(&L);
getchar
();
return
0;
}
|
7、运行结果:
8、由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。
9、希尔排序重点在于选择一个较好的增量序列,一般的初次取序列的一半为增量,以后每次减半,直到增量为1。但是,这样的增量序列不是最佳的,这是一个课题。