算法原理
算法思想
希尔排序(Shell Sort)是插入排序的一种更高效的改进版本,也称为缩小增量排序。它基于增大元素之间的比较与交换间距的思想,先将待排记录表按间隔D分成若干子序列,然后利用插入排序完成子序列内排序,再渐次减小间隔D重复上述分组插入排序操作,直至D=1为止。
算法示例
算法分析
也许,初次接触希尔算法时,会感到奇怪,为什么,希尔排序算法使用了多次的插入排序,复杂度反而更小呢?
因为,越接近有序的序列,插入排序算法所需的比较和移动次数越少。
按最坏的情况考虑,不妨假设 n = 2 k n=2^k n=2k,对长度为 n n n的序列,D依次为 2 k − 1 … 1 2^{k-1}\dots 1 2k−1…1,则,比较次数为 ∑ 2 k − 1 + 2 k − 2 ∗ ( 2 ∗ 2 ) + 2 k − 3 ∗ ( 4 ∗ 4 ) … 1 ∗ 2 k − 1 ∗ 2 k − 1 = ∑ i = 0 k − 1 2 k − 1 + i = 2 k − 1 ( 2 k − 1 ) = n 2 − n 2 = O ( n 2 ) \sum2^{k-1}+2^{k-2}*(2*2)+2^{k-3}*(4*4)\dots1*2^{k-1}*2^{k-1}=\sum\limits_{i=0}^{k-1} 2^{k-1+i}=2^{k-1}(2^k-1)=\frac {n^2-n}{2}=O(n^2) ∑2k−1+2k−2∗(2∗2)+2k−3∗(4∗4)…1∗2k−1∗2k−1=i=0∑k−12k−1+i=2k−1(2k−1)=2n2−n=O(n2)
最好的情况,比较次数为 ∑ 2 k − 1 + 2 k − 2 ∗ ( 2 ∗ 1 ) + 2 k − 3 ∗ ( 4 ∗ 1 ) … 1 ∗ 2 k − 1 = k 2 k − 1 = n 2 log 2 n = O ( n log n ) \sum2^{k-1}+2^{k-2}*(2*1)+2^{k-3}*(4*1)\dots1*2^{k-1}=k2^{k-1}=\dfrac{n}{2}\log_2n=O(n\log n) ∑2k−1+2k−2∗(2∗1)+2k−3∗(4∗1)…1∗2k−1=k2k−1=2nlog2n=O(nlogn)
平均性能介乎二者之间,为 n 1.5 n^{1.5} n1.5
稳定性分析
插入排序是不稳定的,希尔排序可以视为一种特殊的插入排序,自然也是不稳定的。
算法实现
/*按指数递减增量序列进行希尔排序*/
void ShellSort(SqTable &L) {
n = L.Len;
for (d = n/2;d > 2;d /= 2) // 增量n/2、n/4、…
for (j = 1;j <= d;j++) // 对d个子表排序
InsertSort2(&L.r[j],n+1-j,d);
InsertSort2(&L.r[1],n,1);
//ShellSort
void InsertSort2(RcdType A[], int n,int incr) {
for (i = incr; i <= n; i += incr)
for (j=i; (j>=incr)&&(A[j].key<A[j-incr].key);j-=incr)
swap(A[j],A[j-incr]);
}//InsertSort2
/*按任意递减增量序列进行希尔排序*/
void ShellSort(SqTable &L, int dlta[], int t) {
for (int k=0; k<t; ++k)
ShellInsert(L, dlta[k]);
} //ShellSort
/*一趟增量为dk的插入排序*/
void ShellInsert(SqTable &L, int dk) {
for (i=dk+1; i<=L.len; i++)
if (L.r[i].key < L.r[i-dk].key) {
L.r[0] = L.r[i]; // 暂存在L.r[0]
for (j=i-dk; j>0&&L.r[0].key<L.r[j].key; j-=dk)
L.r[j+dk] = L.r[j]; // 交换位置
L.r[j+dk] = L.r[0]; // 插入
}
} //ShellInsert