前言:这个需要先理解插入排序
然后这个希尔排序呢,其实是一种分段分段的插入排序,是把原来的数列按一定的尺度分成几个小的序列进行插入排序,然后缩小尺度继续进行插入排序,直到尺度缩小到1进行最后一次正常的插入排序,就可以得到最后完整的序列。
所以,希尔排序也叫缩小增量排序。(这个增量是针对下表而言的,不是针对数字大小)
思路:
首先我们需要确认尺度,最简单的尺度就是length不断整除二,我们先这么写:
这边有张图非常清晰明了,我就直接摘过来了;
原文链接是:排序:希尔排序(算法) - 简书 (jianshu.com)
1.主要的思路就是按照一定的间隔,我们把原数组分成若干个小的新数组,然后在小的数组中进行插入排序。说到这,有的算法中写的是 for(for(for(if 的结构来展现希尔排序,然而我认为既然你是继承插入排序,最关键那层没必要不和插入排序一样,所以我用的是 for(for(while 的结构来展现希尔排序,也就是最关键的排序部分用的是插入排序。
2.如何选择间隔(gap)?最简单的自然是对length不断进行二分,从而实现,但是会存在弊端,我们放在文章的后半部分。如何进行二分?最直接的自然是 /=2 这个操作,但是用位运算会快一些,我们可以认为 >>=1 运算与 /=2 一模一样。
既然要选择gap,而且gap在每一轮中是不变的,因此gap的变化要放在最外层的for中实现。
3.剩下的一层 for(while 其实我们就是用来实现插入排序的,格式都一样。但是这边的插入排序有些不一样,他并不是一次可以把一个分出来的小数组全部排列完,他是先排前两个,然后自增,排另一个分出来的小数组···直到自增到最先那个小数组的第三个元素,这时才相当于把第一个小数组的前三个元素进行了插入排序。至于为什么要这么实现,自然是不想开辟新的空间,浪费内存。
思路说到这就差不多了,一些细枝末节写在注释中。 下面上代码:
void ShellSort(int *a,int length)
{
int i, j;
int gap,temp;
for (gap = length >> 1; gap >= 1 ; gap >>= 1)
{
for (i = gap; i < length; i++)//这边为什么是自增,看我上头写的3
{
//下面就是一个完整的插入排序
//其余部分为什么都是加减gap,看我上面写的去/doge
temp = a[i];
j = i - gap;
while (j >= 0 && a[j] > temp)
{
a[j + gap] = a[j];
j-=gap;
}
a[j + gap] = temp;
}
}
}
拓展一下:
我们gap序列的选择时length/2,但如果尺度的选取是类似1 2 4 8 这样的话,我们前面的几次插入排序都是徒劳的,效率反而比直接的插入排序要低上许多,所以我们选取尺度序列需要进行一定的考量。这边参考了
排序算法之希尔排序及其增量序列 - ccneko - 博客园
其中提到的Hibbard增量序列:
这个增量序列的递推公式是Dk=2^k−1:{1, 3, 7, 15, 31, 63, 127, 255, 511, 1023...}
然后我们想要借助新的gap序列实现希尔排序,就需要稍微改动一下原来的代码了,首先是我们需要设置个函数开辟个新的数组来存增量(其实也不一定,不过这样比较通用)。
1.在这个创建GAP序列的函数中,我们需要创建一个可以在别的函数调用的数组,因此我们开辟的时候要声明全局变量(静态变量)static。
2.根据所需的长度来生成一些gap值
3.需要返回所创建的gap序列的首地址。‘
因此,函数如下:(这里我依旧采用位运算来代替乘法,可以认为 <<=1 与 *2 作用相仿)
int* Get_Hibbard_Gap(int length)//传入你需要排序的个数,生成gap序列。
{
static int arr[N] = { 0 };
int num = 2;
for (int i = 0; num < length + 1; i++)
{
arr[i] = num - 1;
num <<= 1;
}
return arr;
}
然后只需要在原来的ShellSort稍作更改,就可以应用新序列进行排序,一些细节写在注释里,下面是代码:
void Hibbard_ShellSort(int *a,int length)
{
int i, j;
int temp = 0;
int* gap = Get_Hibbard_Gap(length);
//由于从后往前使用gap序列,我们要先找到gap序列中最后一个数对应的下标
while (gap[temp])//这边就没必要新建个变量了,麻烦一下暂时用不上的temp /doge
temp++;
temp -= 1;
//下面的代码整体上几乎只需要把之前的gap改成gap数组中的值就行,没什么大变化
for (int k = temp; k >= 0; k--)
{
printf("gap = %d\n", gap[k]);//用来测试的
for (i = gap[k]; i < length; i++)
{
temp = a[i];
j = i - gap[k];
while (j >= 0 && a[j] > temp)
{
a[j + gap[k]] = a[j];
j -= gap[k];
}
a[j + gap[k]] = temp;
}
//这个打印用来测试的
for (int i = 0; i < N; i++)
printf("%d ", a[i]);
printf("\n");
}
}
然后这个希尔排序就完成了,下面展示一下我的测试代码:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define N 30
//之前写过了,就不再全写一遍了
void generate_random_number(int*, int, int);
void swap(int*, int*);
int* Get_Hibbard_Gap(int length)//传入你需要排序的个数,生成gap序列。
{
static int arr[N] = { 0 };
int num = 2;
for (int i = 0; num < length + 1; i++)
{
arr[i] = num - 1;
num <<= 1;
}
return arr;
}
void Hibbard_ShellSort(int *a,int length)
{
int i, j;
int temp = 0;
int* gap = Get_Hibbard_Gap(length);
//由于从后往前使用gap序列,我们要先找到gap序列中最后一个数对应的下标
while (gap[temp])//这边就没必要新建个变量了,麻烦一下暂时用不上的temp /doge
temp++;
temp -= 1;
//下面的代码整体上几乎只需要把之前的gap改成gap数组中的值就行,没什么大变化
for (int k = temp; k >= 0; k--)
{
printf("gap = %d\n", gap[k]);//用来测试的
for (i = gap[k]; i < length; i++)
{
temp = a[i];
j = i - gap[k];
while (j >= 0 && a[j] > temp)
{
a[j + gap[k]] = a[j];
j -= gap[k];
}
a[j + gap[k]] = temp;
}
//这个打印用来测试的
for (int i = 0; i < N; i++)
printf("%d ", a[i]);
printf("\n");
}
}
int main()
{
int arr[N + 10] = { 0 };
generate_random_number(arr, 0, 1024);
Hibbard_ShellSort(arr,N);
printf("排序后数列:\n");
for (int i = 0; i < N; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
测试结果:
至此,ShellSort(希尔排序)完成。