1、希尔排序基本思路:
之前的一篇博客中,介绍了直接插入排序的实现,希尔排序实际上是在直接插入排序的基础上优化
当一组数据,越接近有序,那么他需要调整(移动)的次数就越少
(往极端的方向想,如果一组数据是有序的,那么根本就无需排序)
||
\ || /
V
希尔排序的思路:
(1)先通过分组预排序,让一组数据接近有序
(2)再通过直接插入排序,将这组数据重新排序
2、代码实现
下面介绍的顺序是:
(a)先分组
(b)以红组为例,实现调整过程
(c)三个组预排序的整体实现
(三组同时排序,红组排一次,绿组排一次,紫组排一次,然后又回到红组)
(d)直接插入排序
(1)分组
我们使用抽取的方法来将一组数据进行分组,抽取的间隔gap = 3
这样就分成了 红组、绿组、紫组
(2)以红组为例进行排序
调整的方式和直接插入排序的方式 几乎一样,但是需要注意的是,end每次移动的距离是 gap,
而不是1
void ShellSort(int* a, int size, int gap)
{
int end = 0;
int val = a[end + gap]; //第一个注意点:end的下一个位置在 end+gap
while (end>=0)
{
if (a[end] > val)
{
a[end + gap] = a[end]; //第二个注意点
end -= gap; //第三个注意点:end向前移动的时候,移动gap个单位
}
else
{
break;
}
}
a[end + gap] = val; //第四个注意点
}
(3)三组预排序
这里需要注意的两个地方是
(a)每个组的起始位置是连续的
(b)end最远能到达的位置只能是8所在的位置,即 size-1-gap 的位置
void ShellSort(int* a, int size, int gap)
{
for (size_t i = 0; i < size-1-gap; i++)
{
int end = i; //end的起始位置是连续的
int val = a[end + gap]; //第一个注意点:end的下一个位置在 end+gap
while (end >= 0)
{
if (a[end] > val)
{
a[end + gap] = a[end]; //第二个注意点
end -= gap; //第三个注意点:end向前移动的时候,移动gap个单位
}
else
{
break;
}
}
a[end + gap] = val; //第四个注意点
}
}
(4)直接插入排序
经过预排序后,这组数据更加接近有序,将这组数据送入直接插入排序即可
void InsertSort(int* a,int size)
{
for (size_t i = 0; i < size-1; i++)
{
int end = i;
int val = a[end + 1]; //事先记录下每次要插入的数
while (end >= 0)
{
if (a[end] > val)
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = val; //end = -1时,会跳出循环,说明有序数组中的所有元素都比val要小
}
}
void ShellSort(int* a, int size, int gap)
{
/*************************预排序********************************/
for (size_t i = 0; i < size-1-gap; i++)
{
int end = i; //end的起始位置是连续的
int val = a[end + gap]; //第一个注意点:end的下一个位置在 end+gap
while (end >= 0)
{
if (a[end] > val)
{
a[end + gap] = val; //第二个注意点
end -= gap; //第三个注意点:end向前移动的时候,移动gap个单位
}
else
{
break;
}
}
a[end + gap] = val; //第四个注意点
}
/*************************直接插入排序****************************/
InsertSort(a,size);
}
3、结果测试
int main() {
int arr[] = { 3,5,2,6,1,7,4,8,12,10,11,9,15,14,13 };
int gap = 3;
ShellSort(arr, sizeof(arr) / sizeof(arr[0]), gap);
for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
4、预排序优化
预排序的时候,我们设置gap = 3,我们测试一下指针的移动次数
(1)gap与预排序的联系:
gap越大,预排序越快,但是 越不有序
gap越小,预排序越慢,但是 越有序
(2)思路
我们可以多次预排序,每次预排序结束,gap = gap/2,直到gap = 1
gap较大的时候,预排序很快
随着gap减小,虽然慢,但更接近有序
(3)代码实现
我们将gap的初始值设置为size,每经过一次预排序,gap除以2
直到gap = 1(直接插入排序)
void ShellSort(int* a, int size)
{
int gap = size / 2;
while (gap>1)
{
gap /= 2;
for (size_t i = 0; i <= size - 1 - gap; i++)
{
int end = i; //end的起始位置是连续的
int val = a[end + gap]; //第一个注意点:end的下一个位置在 end+gap
while (end >= 0)
{
if (a[end] > val)
{
a[end + gap] = a[end]; //第二个注意点
end -= gap; //第三个注意点:end向前移动的时候,移动gap个单位
p++;
}
else
{
break;
}
}
a[end + gap] = val; //第四个注意点
}
}
InsertSort(a, size);
}
(4)代码测试结果
排序的个数比较少的时候,前后gap的设置方法没有特别明显的差异
5、时间复杂度
希尔排序的时间复杂度计算十分复杂,这里就直接给出结果
希尔排序的移动次数和gap有关,平均移动次数是 N^1.3
时间复杂度接近 O(N)