希尔排序(shell sort)是对直接插入排序的一种改进,改进的着眼点是:(1)若待排序记录按关键码基本有序,直接插入排序的效率很高;(2)由于直接插入排序算法简单,则在待排序记录个数较少时效率也很高。
算法思想:
希尔排序也称为“缩小增量排序”。它的基本思想是:首先将待排序的元素分成多个子序列,使得每个子序列的元素个数相对较少,对各个子序列分别进行直接插入排序,待整个待排序序列“基本有序后”,再对所有元素进行一次直接插入排序。
具体的排序过程是:假设待排序的记录个数为n个,先取整数d(d < n),例如,取 d = n/2(向下取整),将所有相距为d的记录构成一组,从而将整个待排序记录序列分割成为d个子序列。对每个子序列分别进行直接插入排序,然后再缩小间隔d。例如,取d = d/2(向下取整),重复上述分割,再对每个子序列分别进行直接插入排序,直到最后d = 1,即将所有记录放在一组进行一次直接插入排序,最终将所有记录重新排列成按关键码有序的序列。
补充:
(1)子序列的构成不能是简单的“逐段分割”,而是将相距某个“增量”的记录组成一个子序列,这样才能有效地保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
(2)开始时,增量的取值较大,每个子序列中的记录个数较少,并且提供了记录跳跃移动的可能,排序效率较高;后来增量逐步缩小,每个子序列中的记录个数增加,但已基本有序,效率也较高。
(3)在每个子序列中,待插入记录和同一子序列中的前一个记录比较,在插入记录 arr[i] 时,自 arr[i - d]起往前跳跃式(跳跃幅度为d)查找待插入位置,在查找过程中,记录后移也是跳跃d个位置。在整个序列中,前d个记录分别是d个子序列中的第一个记录,所以从第d+1个记录开始进行插入。
(4)退出内层循环,说明找到了插入位置,因为arr[j]刚刚比较完毕,所以,j+d为正确的插入位置,将待插入记录插入。
具体实现如下,详见代码注释:
#include <iostream>
void shellSort(int arr[], int len)
{
int i, j, d, temp;
for (d = len/2; d > 0; d = d/2) // 当d=1时,所有元素作为一个序列(长度为len),进行最后一次排序。
{
for (i = d; i < len; i++) // 在整个序列中,前d个记录分别是d个子序列中的第一个记录,所以从第d+1个记录开始进行插入。(第d+1个记录,对应的数组下标是d,所以从 i=d 开始)
{
temp = arr[i]; // 注意点1;取出当前子序列的待插入元素,存入temp中
for (j = i - d; j >= 0; j -= d) // 在每个子序列中,待插入记录和同一子序列中的前一个记录比较,在插入记录 arr[i] 时,自 arr[i - d]起往前跳跃式(跳跃幅度为d)查找待插入位置,在查找过程中,记录后移也是跳跃d个位置。(j>=0 是防止数组越界)
{
if (temp < arr[j]) // 若“待插入数据temp”比当前的数据小,则把当前的数据后移d个位置。若发生了后移,则会覆盖掉原数组中“此次待插入的数据arr[i]“的值,但是没影响,因为arr[i]已经保存在temp中了。
{
arr[j + d] = arr[j];
}
else
break;
}
arr[j + d] = temp; // 退出内层循环,说明找到了插入位置,因为arr[j]刚刚比较完毕,所以,j+d为正确的插入位置,将待插入记录插入。
}
}
}
int main(int argc, const char * argv[]) {
int arr[] = {2,5,8,1,3,6,9,0,4,7};
int len = sizeof(arr)/sizeof(int);
shellSort(arr, len);
for (int i = 0; i < len; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}