希尔排序,又称为“缩小增量排序”,是直接插入排序的优化。
对于直接插入排序,当待排记录序列处于正序时,时间复杂度可达O(n),若待排记录序列越接近有序,直接插入排序越高效。希尔排序的思想正是基于这个点进行优化。
基本思想
希尔排序基本思想:将整个待排记录序列, 按一定的增量分割成若干子序列,对这些子序列分别执行直接插入排序,执行完后整个序列较执行前更接近正序, 此过程称为一趟希尔排序。然后减小增量再次以同样的方法处理整个待排记录序列,重复上述过程,待整个序列中的记录“基本有序”时,再对全体记录执行一次直接插入排序。
步骤展示
按一定的增量gap,将序列分割成若干子序列。希尔排序并没有要求增量是某些具体值,意味着我们可以任意取。但增量要较为合适的选取,此处gap = 5, 也就是每间隔5个为一组。
现在我们对每组序列进行直接插入排序
显然一趟希尔排序后,序列要比操作前更接近正序。缩小增量继续上述过程,则待排记录序列将比此时更接近正序。
第二趟希尔排序开始:
1.分组:将增量缩小到多少也是一个问题,一趟缩小一半,一趟缩小三分之一…都是可以的。
2.对每组进行直接插入排序
第三趟希尔排序开始:
该序列此时已经基本接近有序,我们可以对它进行直接插入排序。事实上,直接插入排序也就是增量gap = 1时的一趟希尔排序。若使用希尔排序,最后必定要对序列进行一趟直接插入排序,目的是为了使该序列严格有序。
代码实现
//希尔排序,排升序
void ShellSort(int* a, int n)
{
assert(a);
}
第一步:我们首先尝试实现,对第一组的直接插入排序,假设增量gap = n / 2。
void ShellSort(int* a, int n)
{
assert(a);
//1.尝试对第一组进行直接插入排序
int gap = n / 2;
for (int i = 0; i < n - gap; i += gap)
{
//单趟直接插入排序, 在[0, end]区间插入x
int end = i;
int x = a[end + gap];
while (end >= 0)
{
if (a[end] > x)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
//插入
a[end + gap] = x;
}
}
第二步:尝试对增量gap = n / 2时, 所有组进行直接插入排序
void ShellSort(int* a, int n)
{
assert(a);
int gap = n / 2;
//尝试对多组进行直接插入排序
for (int j = 0; j < n - gap; j++)
{
//对一组进行直接插入排序
for (int i = j; i < n - gap; i += gap)
{
//单趟直接插入排序, 在[0, end]区间插入x
int end = i;
int x = a[end + gap];
while (end >= 0)
{
if (a[end] > x)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
//插入
a[end + gap] = x;
}
}
}
将前面所使用的值作为测试
成功匹配上了。
以上便是一趟希尔排序。
第三步:缩小增量,重复执行上述逻辑
void ShellSort(int* a, int n)
{
assert(a);
int gap = n;
while (gap > 1)
{
gap = gap / 2;
//尝试对多组进行直接插入排序
for (int j = 0; j < n - gap; j++)
{
//对一组进行直接插入排序
for (int i = j; i < n - gap; i += gap)
{
//单趟直接插入排序, 在[0, end]区间插入x
int end = i;
int x = a[end + gap];
while (end >= 0)
{
if (a[end] > x)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
//插入
a[end + gap] = x;
}
}
}
}
这里while循环内的判断条件是gap > 1, 当增量gap = 2或者gap = 3时, 进入循环执行gap = gap / 2
后,增量gap就会变为1, 也就是对整个序列进行一次直接插入排序以保证达到严格有序的目的。
这种方式是逐个组逐个组地进行排序,也就是一个组排好序以后,再排下一个组。我们也可以采用轮流法,一个组排一次。则代码如下, 使用gap = gap / 3 + 1
保证了最后一次gap必定是1,即保证了最后必定对整个序列使用一次直接插入排序。
//希尔排序,排升序
void ShellSort(int* a, int n)
{
assert(a);
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
//单趟希尔排序
//多组直接插入排序
//将x插入[0, end]区间
for (int i = 0; i < n - gap; i++)
{
int end = i;
int x = a[end + gap];
while (end >= 0)
{
if (a[end] > x)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = x;
}
}
}
希尔排序的平均时间复杂度约为O(n ^ 1.3)