前面的直接插入算法,以抓牌为例,假设手气比较好,抓牌的顺序是从A直到K,那么在这整个过程中都无需进行任何的插入动作,只需要将每次抓到的牌放在最后即可。假设现在手上已经抓到的牌的顺序中逆序比较少,则抓牌的过程中插入操作就越少。因此,对于基本有序的序列排序,直接插入排序的性能是比较好的。这里的基本有序是指较大的数据尽量在后面,而较小的数据尽量在前面。为了使得直接插入算法更加有效,我们可以在最后使用插入算法之前,使整个序列基本有序。
要使整个序列基本有序,我们采取的方法是采用跳跃分割的策略:将相距某个“增量”的元素组成一个序列,然后不停地变换这个增量,这样才能保证在子序列内分别进行排序得到的结果是基本有序而不是局部有序。当然,对子序列进行排序的时候也可以使用直接插入排序算法。以图为例,假设增量为increment,当增量为1的时候,即相距为1的元素组成一个序列,这表示子序列只有一个,就是当前序列;当增量为2的时候,即相距为2的元素组成一个序列,这表示子序列将有两个;因此,当增量为increment的时候,子序列将有increment个。过程中,我们将increment不断缩小直到1,对于每次的increment,我们将要对这increment个子序列分别进行排序。
这里有几点需要说明一下:途中同种颜色为指定增量下的子序列,可以看出子序列的个数为increment;另外,对于increment的初始值,我们设为元素个数对2取整(n/2)。因为假设增量大于一半了,那么基本上每个子序列就只有一个元素了。
我们将这个过程量化解释一下:假设需要进行排序的序列个数个n,则将增量初始化为n/2,即increment = n/2,我们每次都需要式increment减1,从n/2直到1(最外层for循环);然后对于每个增量,里面有increment个子序列,需要对这increment个子序列分别进行排序(第二个for循环)。
代码如下:
void ShellSort(int array[], int arrayCount)
{
int increment = arrayCount/2; //增量
int subseqNo; //
for(; increment > 0; --increment) //增量能取的个数
{
for(subseqNo = 0; subseqNo < increment; ++subseqNo) //当增量为increment时,子序列的个数,用subseqNo来做每个子序列首个元素下标
{
//接下来对每个子序列进行插入排序
for(int i = subseqNo + increment; i < arrayCount; i += increment)
{
if(array[i] < array[i - increment]) //i处元素寻找插入位置
{
int temp = array[i], j;
for(j = i - increment; j >= subseqNo; j -= increment)
{
if(array[j] > temp) //遇到比temp大的就后移一位
array[j + increment] = array[j];
else
break; //即当j出元素<temp,则j后面则为插入位置
}
array[j + increment] = temp; //要么是break出来,要么是j到头了(多减了)
}
}
}
}
}
可以看出希尔排序其实就是改进后的直接插入排序,它的每一步直接插入排序都将原序列趋于基本有序,因此,使得后面的插入排序更加方便。