介绍
希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),也称递减增量排序算法,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。
要点
- 分组增量要初始需要为数组的一半,后续每次减半,需要使用for循环实现
- 分组进行插入排序,需要使用while进行循环
- 所谓的分组,其实就是循环的时候隔着固定的增量而已,也就是类似于
i + 2
这种 等差数列
代码
public static int[] arr = new int[] { 64, 13, 57, 11, 27, 93, 34, 98, 78, 50 };
public static void InsertSort()
{
int length = arr.Length;
int temp;
// step初始为5,然后只要满足step >= 1的条件,就开始走循环内部,循环内部走完,再走ste /= 2
// 这样就能控制step增量每次减半,然后直到增量为1
for (int step = length / 2; step >= 1; step /= 2)
{
// 从i=step开始,依次增加
for (int i = step; i < length; i++)
{
// 暂时记录arr[i]的数值
temp = arr[i];
// 因为要跨增量分组进行插入排序, 所以把i和i-step的分为一组,然后两者进行插入排序
// 比如i=5会和i=0的为一组,这个分组会受增量的控制
int j = i - step;
// 这里是重点,这里会将分组的数据进行插入排序,比如j = 4,step= 2
// 如果满足arr[4] > arr[6]以及j>=0, 那么先会将arr[6]等于arr[4]
// 然后将j 减去 step,就是4 - 2 = 2,又会将arr[2]和arr[6]进行对比,满足要求就继续下去,那么arr[4]就等于arr[2]
// 以此类推,直到不满足要求为止
while (j >= 0 && arr[j] > temp)
{
arr[j + step] = arr[j];
j -= step;
}
// 这时候j = -2, 因为arr[4]和arr[6]都比temp大,所以最后需要把arr[0]的值改成temp,
arr[j + step] = temp;
}
Print(step);
}
}
运行过程打印如下:
增量为5:64, 13, 57, 11, 27, 93, 34, 98, 78, 50,
增量为2:27, 11, 34, 13, 57, 50, 64, 93, 78, 98,
增量为1:11, 13, 27, 34, 50, 57, 64, 78, 93, 98,
总结
为什么会有人说希尔排序算是对插入排序的一种优化。我接下来展示两串代码:
这个是作者之前的插入排序一文当中的一部分代码,暂且称作CR1:
for (j = i-1; j >= 0 && temp < array[j]; j--)
{
// 将比temp大的数集体往后移动
array[j + 1] = array[j];
}
// 最后找到比temp小的数,然后往这个数的后面一个下标插入temp数
array[j + 1] = temp;
接下来我们看看本文当中的一部分代码,我们称作CR2:
int j = i - step;
while (j >= 0 && arr[j] > temp)
{
arr[j + step] = arr[j];
j -= step;
}
arr[j + step] = temp;
那么我们把这个代码换一种写法,称作CR3,大家看看:
int j = 0;
for (j = i - step; j >= 0 && temp < arr[j]; j -= step)
{
arr[j + step] = arr[j];
}
arr[j + step] = temp;
这回是不是瞬间一目了然了,CR1和CR3何其的相似。,这两部分都是插入排序,也都是精髓点,两者都是在设定为有序序列里面查找temp
应该放的位置,也就是满足arr[j] < temp && arr[j+2] > temp
条件的地方,而这个地方就是arr[j+1]
或者是arr[j+step]
,然后把temp
的值,也就是arr[i]
的值直接插入到这个位置,成功的放入有序序列。
所以以后遇到说有用到插入排序方法的代码,八九不离十的离不开这种写法,大概的代码样式就是如下:
for(初始化 j, 设置j的限制条件(比如j>0&&temp<arr[j]), 设置j的递减/递增的间隔数)
{
arr[j+/-间隔数] = arr[j];
}
arr[+/-间隔数] = temp
希尔排序总的来说,算是比较难写出来的,思路好懂,但是不一定写的出来。尤其是当遇到分组的时候,我第一次听到分组,下意思的就会去想用一个新的数组来记录,然后再把这个数组进行排序。但是希尔排序是内部排序的算法,也就是不需要动用新数组的,这也是希尔排序空间复杂度为**O(1)**的原因。
目前作者只能总结出这么多,可能因为作者技术有限,后面遇到再更新吧。