希尔排序
希尔排序步骤
希尔排序的核心还是插入排序,但是把插入排序分成两部分,1.预排序2.插入排序。先对原数组进行预排序,使数组接近有序(让更大的数字和更小的数字更快的分配到两边),然后再对已经接近有序的数组进行插入排序,这样就会快很多。
时间复杂度约为:O(N^1.3);空间复杂度O(1);由于希尔排序有分预排序,所以有可能对相同大小的数字会改变他们的相对位置,是不稳定排序。
预排序:把原数组分组,然后对不同的分组进行插入排序,例如下面这串逆序的数字,我们对其分组分为3组,先进行预排序,让数组接近有序。
不断地进行预排序,直到我们取的gap=1(gap表示同组数据之间的间距),就是直接插入排序,这样我们就很好的优化了直接插入排序的时间复杂度,希尔排序就是直接插入排序的进阶版。
我们用gap表示一组的间隔,这样总共就有gap组(上述案例gap=3)。当gap过大时,较大的大的数和较小的数就能更快地跳到两边,但是相对的,一次gap排完后数组也更不接近有序;当gap过小时,就约等于直接插入排序,遇到数据过多时,希尔排序就失去了原本的意义。所以一般先给gap赋值n(总数)再取gap=gap/2或者gap=gap/3+1(gap除2的最后一次gap一定是1,gap除3的最后一次可能会是0,为了让最后一次gap一定为1,所以再+1),这样就刚好达到了一个平衡。(gap=1就是直接插入排序)
总结:希尔排序核心还是是直接插入排序,差别在于直接插入排序就是gap=1,而希尔排序是gap从n/2或者n/3+1开始不断地趋于1,最终gap=1进行一次直接插入排序就完成了。但希尔排序内部还有不同写法,下述为希尔排序的两种写法。
希尔排序写法
单组排序:
排完一组再重新返回起点,再后移一位排下一组,总共排gap组。
多组并排:
每次排序完后往后移动一位,排下一组,当向后移gap位后又重新排第一组。即i=0时是一组,i++后又是一组,间隔是gap,(i=0和i+=gap是同一组)
计算希尔排序时间复杂度
我们先假设n(总数)很大,外面的时间复杂度就为log3n,接着计算内部预排序的时间复杂度。由于n很大,那么第一次gap=n/3+1也会很大,我们忽略后面的+1,这样相当于有n/3组,每组有3个元素,当逆序情况时,每组要移动1+2=3次,那么累计的次数就是n/3*3=n。
然后gap在除3,gap=gap/3+1,我们在忽略+1,就得到了gap=n/9,相当于分成了n/9组,每组要移动(1+2+3+...+8)=36次,再当当前数据还是逆序,那么累计次数为n/9*36=4n,看起来是变大了的,但实际上因为我们移动过了原逆序数据,第二次移动时肯定不为完全逆序,次数肯定会减少。
这样一直减小gap,知道gap=1时,就是直接插入排序,这时由于我们的数据经过上述预排序后已经变得非常接近有序了,所以累计次数就相当于n(走一遍数组,调一小部分),总时间复杂度就为上述累计次数相加,时间复杂度大约为O(N^1.3)
希尔排序代码
单组排序:
//希尔排序
void ShellSort(int *arr, int n)
{
int gap = n;
//重新上来时gap=1就说明上次排序是直接插入排序
while (gap > 1)
{
//gao有两种取法:如下
//gap = gap / 2;
gap = gap / 3 + 1;
//单组排序
//一组一组排序,排完一组再排下一组,总共排gap组
for (int j = 0; j < gap; j++)
{
//每组下标i+=gap,向后移gap位
for (int i = 0; i < n - gap; i += gap)
{
//直接插入
int end = i;
int tmp = arr[end + gap];
while (end >= 0)
{
if (tmp < arr[end])
{
arr[end + gap] = arr[end];
end -= gap;
}
else
break;
}
arr[end + gap] = tmp;
}
}
}
}
多组并排:
//希尔排序
void ShellSort(int *arr, int n)
{
int gap = n;
//重新上来时gap=1就说明上次排序是直接插入排序
while (gap > 1)
{
//gao有两种取法:如下
//gap = gap / 2;
gap = gap / 3 + 1;
//多组并排,i=0时是一组,i++后又是一组,间隔是gap,(i=0和i+=gap是同一组)
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = arr[end + gap];
while (end >= 0)
{
if (tmp < arr[end])
{
arr[end + gap] = arr[end];
end -= gap;
}
else
break;
}
arr[end + gap] = tmp;
}
}
}