第一个突破O(n^2)的排序算法。
流程
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 每次排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,对各个子序列分别进行插入排序。
- 进行k次2过程。
- 仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
由于开始时,t的取值较大,每个子序列中的元素较少,排序速度较快,到排序后期t取值逐渐变小,子序列中元素个数逐渐增多,但由于前面工作的基础,大多数元素已经基本有序,所以排序速度仍然很快。
演示
取t=t/3向下取整+1。
编号 | a[] | 操作 | |
---|---|---|---|
0. | 第一轮 | 9 3 5 4 1 2 | 增量为3,子序列为9 4,进行插入排序。 |
1. | 第一轮 | 4 3 5 9 1 2 | 增量为3,子序列为3 1,进行插入排序。 |
2. | 第一轮 | 4 1 5 9 3 2 | 增量为3,子序列为5 2,进行插入排序。 |
3. | 第二轮 | 4 1 2 9 3 5 | 增量为2,子序列为4 2 3,进行插入排序。 |
4. | 第二轮 | 2 1 3 9 4 5 | 增量为2,子序列为1 9 5,进行插入排序。 |
5. | 第三轮 | 2 1 3 5 4 9 | 增量为1,子序列为2 1 3 5 4 9,进行插入排序。 |
6. | 结束 | 1 2 3 4 5 9 |
代码
#include <stdio.h>
#define N 100001
int n,a[N];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;++i) scanf("%d",&a[i]);
for (int t=n/3+1;t!=1;t=t/3+1)
{
for (int i=t+1,j;i<=n;++i)
{
int x=a[i];
for (j=i-t;j>=1;j-=t)
if (a[j]>x) a[j+t]=a[j];
else break;
a[j+t]=x;
}
}
for (int i=2,j;i<=n;++i)
{
int x=a[i];
for (j=i-1;j>=1;--j)
if (a[j]>x) a[j+1]=a[j];
else break;
a[j+1]=x;
}
for (int i=1;i<=n;++i) printf("%d ",a[i]);
}
时间复杂度分析
对希尔排序的时间复杂度分析很困难,在特定情况下可以准确的估算排序码的比较次数和元素移动的次数,但要想弄清楚排序码比较次数和元素移动次数与增量选择之间的依赖关系,并给出完整的数学分析,还没有人能够做到。
增量的取法有各种方案。最初shell提出取t=n/2向下取整,t=t/2向下取整,直到t=1。但由于直到最后一步,在奇数位置的元素才会与偶数位置的元素进行比较,这样使用这个序列的效率会很低。后来Knuth提出取t=n/3向下取整+1。还有人提出都取奇数为好,也有人提出t互质为好。应用不同的序列会使希尔排序算法的性能有很大的差异。