深入理解希尔排序
希尔排序由来
希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing IncrementSort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959年提出而得名。–源自百度百科
- 希尔排序其实就是一种经过优化的直接插入算法,这种优化使得这种插入排序算法的时间复杂度得到了质变,(这点我们会在下文验证),但其在一些特殊情况下也存在缺陷。
希尔排序思想(升序举例)
- 要想更好地理解希尔排序,就必须先理解直接插入排序,相比于希尔排序,直接插入排序的思想就简单地多,我们几乎都完成过直接插入排序,逢年过节和三五朋友常常使用棋牌或者麻将进行娱乐,当我们摸到牌以后会自然地去将牌面进行排序,而整理好牌面后再次摸牌得到一张牌插入到牌堆中,这种排序就是直接插入排序。
给定一个整型升序数组a,向其中插入一数字x,如下:
int a[]={2,3,4,6,8,9}
//向a中插入x=5应该如何做?
显然,遍历数组a,找到比x小的第一个值,然后将其插入至x后,同时不影响数组中其它值,故从数组末尾向头遍历
为了方便理解,我们先实现插入一个数字,就使用上述用例:
int end;//数组尾下标
int x;//要插入的数字
while(end>=0)//数组只剩一个元素或者没有元素
{ //倒序遍历
if(a[end]>x) //如果元素大于x则后移
{
a[end+1]=a[end];
end--;
}
else
{
break; //当元素小于x或者数组走到尽头时直接将end的后一位置赋值为x
}
}
a[end+1]=x;
- 插入排序初步思路形成,显然我们的目的不是真的插入一个数进入有序数组,而是在数组中找数进行插入排序,故end和x的值需要确定。
- 观察单趟排序的代码,为了防止数组越界且保证每个值都被读取一次,end选择为数组每个元素的下标,当数组有n个元素,其最大为n-2,x选择为end后一位元素的值。这样做就是相当于将x插入[0,end]的区间中。显然使用for循环就能很好地完成这一切,至此,插入排序就完成了:
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end=i;
int x=a[end+1];
while (end >= 0)
{
if (a[end] > x)
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = x;
}
}
- 显然,此排序的时间复杂度为O(N^2) ;
- 希尔排序的思想在此基础上建立:是否能过通过一些操作,让序列更接近于有序然后再进行插入排序大幅度缩减其时间复杂度?
- 希尔的思想:希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。
希尔排序实现
设定步长gap,可以将数组分为gap组,然后每个组内进行插入排序使其组内有序,最后整体使用一次插入排序,这时插入排序对一个接近有序的数组进行排序,效率就能很好地体现出
假设步长gap为5,随机给定一个数组,如图:
数组被分成5组,然后每个组内进行插入排序得到:
然后再此基础上再次使用一次插入排序,插入排序的时间复杂度会大大减小(前提是大量的数据,否则难以直接观察到):
思想上,我们是组内一组组进行插入排序,实际实现时只需要每遇见一个元素,然后与其关联为gap步的元素进行插入排序,而直接插入排序就可以理解为希尔排序中gap步长为1的特殊情况,代码应运而生:
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 2;
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;
}
}
}
这里还有最后一个需要确定的元素就是gap的取值,经过大量测试,发现当数组越无序,gap越要取大,越接近有序gap越取小,故gap不是一个不变的值,这里最外层使用while循环控制,每次预排序都进行gap=gap/2;这使得gap等于1时最后对整个数组进行真正的插入排序就结束。
希尔排序的时间复杂度需要用数学知识来计算,这里附上《数据结构-用面相对象方法与C++描述》一书中殷人昆老师的计算和结论: