一.shell排序原理
希尔排序(Shell Sort),是D.L.Shell于1959年提出的一种算法。希尔排序是突破O(n^2)时间复杂度的第一批算法。
shell排序是在直接插入排序的基础上进行改进的。将原有大量记录数的记录分成若干个小组,在小组内部进行直接插入排序,当整个序列基本有序时(注意只是基本有序),再对全部记录进行一次直接插入排序。
什么是基本有序呢?
所谓的基本有序,就是小的关键字基本在前面,大的关键字基本在后面,不打不小在中间。
举个例子,像{2,1,3,6,7,5,8,9}这样就可以称为基本有序了。
我们分割待排序记录的目的就是减少待排序记录的个数,并将整个序列向基本有序发展。而分完组后就各自排序的方法达不到我们的要求。因此我们采用跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
二.shell排序算法
代码如下:
void shellsort(Sqlist *L)
{
int i,j;
int increment = L->length;
do
{
increment = increment /3 + 1;
for(i = increment+1;i<=L->length;i++)
{
if(L->r[i]<L->r[i-increment])
{
L->r[0] = L->r[i];
for(j = i - increment;j > 0 && L->r[j];j-=increment)
L->r[j+increment] = L->r[j];
L->r[j + increment] = L->r[0];
}
}
}while(increment > 1);
}
我们来看看算法的思想和步骤。
1.假设现在有数组L,其中的数组元素为{0,5,9,7,8,3,6,2,1},则L->length = 9,我们可以单独定义一个变量来存储数值交换值的中间值,也可以在开辟数组时多定义一个空间,让L[0]来存放中间变量。
这里L[0]我们用来存放中间变量。
2.现在定义一个增量,初始值赋值为待排序的记录数。然后根据记录数选择合适的跨度缩小增量。
例如这里我们可以令增量increment为(increment/3)+ 1 ;则increment为4;
3.现在开始比较第一个与相隔increment个的元素的大小。
第一次为L[1]与L[5]的比较,0<8,不进行交换,第二次,5>3,进行交换,第三次,9>6,进行交换,第四次,7>2,进行交换,还有第五次,L[5]与L[9]的比较,8>1,进行交换。
第一次循环后数组变为:
4.然后再进行循环,increment =(increment/3)+1 = 2。
第一次,L[1]与L[3]比较,0<6,不交换,第二次,3>2,交换,第三次,6>1,交换,第四次,2<5,不交换,第五次,6<9,不交换,第六次,5<7,不交换,第七次,9>8,交换。第二次循环后数组为:
5.再经过一次循环,increment = (increment/3)+1 = 1。
第一次,L[1]与L[2]比较,0<2,不交换,第二次,2>1,交换,第三次,2<3,不交换,第四次,3<6,不交换...以此类推,最终得到数组:
三.shell排序复杂度分析
通过对算法的分析,我们知道希尔排序不是随便分组各自排序,而是将相隔某个增量的记录组成一个子序列,实现跳跃式的移动,使得排序的效率更高。
增量的选取非常关键,迄今为止没人找到一种最好的增量序列。不过大量的研究表明,增量序列为dlta[k] = (2^t-k+1)-1 (0<=k<=t<=[log2(n+1)])时有不错的效率。时间复杂度为O(n^3/2),要好于O(n^2)。注意,增量序列的最后一个增值必须等于1才行。另外,因为记录为跳跃移动的,希尔排序并不是稳定的排序。