希尔排序的本质是对插入排序的优化,如果对插入排序记不太清了,可以去看下我的这篇文章,链接如下:https://blog.csdn.net/ICer_WangV/article/details/122527449?spm=1001.2014.3001.5502
插入排序算法,由于需要将待排序的数据和数组中的元素从后往前比较,然后插入数组当中。那么最坏的情况就是插入的元素是最小的,这就需要将原有的元素都后移(每次插入都需要将数组遍历一次),这就造成插入排序算法有较高的时间复杂度。
那么我们来举个例子讲一下如何去优化:
现在有一个待排数组:
我们首先引入一个概念,叫做“步长”,以步长为间隔,将待排数组进行分组,初始步长通常为为:数组元素个数 /2。这里共有10个元素,则步长为5。将待排数组以5为间隔,分为如下5组:
然后将这五个组,视为5个含有两个元素的待排数组,在组内进行插入排序。
然后,再将步长设为4,重新分为4组(注意看颜色变化)
同样的,再将这四组数据进行插入排序
以此类推,步长为3,分为三组,插入排序
步长为2,分为两组
最后,步长为1,进行最后一次排序,就得到了一个有序的数组
整个的排序的过程如下:
做个总结:初始步长设为元素个数/2,每次排序后,步长递减,。每次排序都以步长为间隔给所有元素分组,组内做插入排序。
这样优化后,插入排序时需要后移的元素(或者说需要遍历的元素)就少了很多,以此降低时间复杂度 ,优化过的算法即为shell算法。
代码实现:
#include<iostream>
using std::cout;
using std::endl;
void traverse(int* a, int n);
void shell_sort(int a[], int len);
int main()
{
int a[9] = { 23,45,65,12,43,9,87,98,76 };
traverse(a, 9);
cout << endl;
shell_sort(a, 9);
traverse(a, 9);
return 0;
}
void shell_sort(int a[],int len)
{
int step;
int value;
int i;
int j;
for (step = len / 2; step > 0; step--)
{
for (i = 0; i < len - 1; i++)
{
value = a[i + 1];
for (j = i + 1; j >= 0 && value < a[j - 1]; j -= step) //插排
{
a[j] = a[j - step];
}
a[j] = value;
}
}
}
void traverse(int* a, int n)
{
for (int i = 0; i < n; i++)
cout << a[i] << " ";
}
核心代码那里和插排有点相似,分组是通过 j -= step来实现的。最初的想法是创建vector动态数组,用来存储每个分组的元素,但是太过复杂了,通过学习,发现在插排上面做一些改动,代码实现简单,还好理解。
运行结果如下: