关于希尔排序及其增量序列的初步探索
P1177 题目传送门
关于希尔排序及其增量序列的初步探索
引言
众所周知,每一个萌新
这也导致了直接插入排序在利用
而一些其他的更好的排序算法也是由这三种排序算法优化而来,比如快速排序是冒泡排序的优化,而堆排序是选择排序的优化。
而接下来讲的希尔排序则是直接插入排序的优化。作为排序算法史上第一个突破
考虑直接插入排序的特点,其
- 在数列基本有序的时候速度较快。
- 由于数据每次只能移动一位,导致其效率较低。
希尔排序就是根据这两项特点做出了改进。
关于希尔排序
希尔排序(英语:随蝴蝶一起消散吧
- 稳定性:不稳定
- 时间复杂度:
O(n\log n)?\sim O(n^2) 详见增量序列 - 空间复杂度:
O(1)
希尔排序通过以下几个步骤来提升插入排序的性能。
- 首先,将数列按照一定的增量或梯度或步长(随便怎么叫)分割成一定的子序列。
- 对每一个子序列进行直接插入排序。
- 缩减增量并回到步骤
1 直到增量为1 (即对整个数组作为一个子序列进行直接插入排序)
这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的增量进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
假设有一个很小的数据在一个升序数列的末端。如果用复杂度为
大白话:让需要大幅度移动的数据飞起来快速向前移动(克服特点
稳定性
在每一次插入排序中,数据是稳定的。但是,相同的数据可能会在各自的插入排序中移动,导致其稳定性被打乱。
增量序列
由以上步骤我们可以得知,我们的增量需要逐步变小,而理论上,任何一个以
\texttt{Shell} 增量序列 \texttt{(1959)}
-
(\frac n 2,\frac n 4,\ldots,1) - 时间复杂度:最坏
O(n^2) 。 - 顾名思义,这个序列是
\texttt{Donald Shell} 提出该算法时使用的数列,速度很拉不建议使用。
ps:我们很容易想到,一个好的增量序列中的数大概应该是两两互质的,因为这样每次的子序列都是不同的,有利于数据快速向前移动。
pps:不知道从什么时候开始,增量序列变成由 1 起始了
\texttt{Hibbard} 增量序列 \texttt{(1963)}
-
(1,3,7,\ldots,2^k-1) - 时间复杂度:最坏情况
O(n^{\frac 3 2}) ,平均情况O(n^{\frac 5 4}) 。 - 一个常用的增量序列。
\texttt{Knuth} 增量序列 \texttt{(1970)}
-
(1,4,13,\ldots,\frac{3^k-1}2) - 时间复杂度:平均情况
O(n^{\frac 3 2}) 。 - 就是不断地乘三加一,一个同样常用的增量序列。
\texttt{Sedgewick} 增量序列 \texttt{(1982)}
-
(1,5,19,41,109,209,\ldots) 该序列偶数为
9\times 4^i-9\times 2^i+1 ,奇数为2^{i+2}\times(2^{i+2}-3)+1 ,其中i 表示第i 个偶数(或奇数,从0 开始)。 - 时间复杂度:最坏情况
O(n^{\frac 4 3}) ,平均情况O(n^{\frac 7 6}) 。 - wikipedia 称其为已知最好的步长序列,但一部分实践表明
\texttt{Ciura} 增量序列或许有更高的性能。
\texttt{Ciura} 增量序列 \texttt{(2001)}
-
(1,4,10,23,57,132,301,701,1750,\ldots) - 时间复杂度:最坏情况
O(n^{\frac 4 3}) ,平均情况:? 。 - 这是基于某些经验规则进行优化的增量序列,其具体的递推关系式并没有明确给出,但通过实测,Ciura序列表现出较为优秀的排序性能。Ciura 增量序列被认为是现代应用中最有效的增量序列之一。该序列已知的部分最大为
1750 ,其后的部分每一个数大约是前一个数的2.25 倍。
后记
这时候就有小天才会问了,哎你这时间复杂度还没证呢。实际上,希尔排序的时间复杂度很多都是猜想反正我不会证,会证的小天才可以分享一下()。
这时候又有小天才会问了,哎比如当
很显然不是这样的,因为 基于比较的排序算法的时间复杂度下界是 这个真能证。
代码
给出使用
#include<bits/stdc++.h>
using namespace std;
class P1177
{
public:
class _MyIn
{
public:
using _I=uint32_t;
static constexpr bool sign=0;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,BZ,stdin),p1==p2)?EOF:*p1++)
#ifdef getchar
static constexpr int BZ=1<<20;
char buf[BZ],*p1=buf,*p2=buf;
#endif
_I operator()()
{
_I n=0;
bool b=0;
char c=getchar();
while(c<48||c>57)((sign&&c=='-')?b=1:0),c=getchar();
while(c>=48&&c<=57)
n=(n<<1)+(n<<3)+(c^48),c=getchar();
return b?-n:n;
}
_MyIn& operator>>(_I &n){return n=operator()(),(*this);}
#undef getchar
}read;
class _SHELL_SORT
{
uint32_t a[16];
public:
constexpr _SHELL_SORT():a()///Sedgewick_init
{
for(int p=0,i=0;i<16;++p)
a[i++]=9*(1<<(p<<1))-9*(1<<p)+1,
a[i++]=(1<<(p+2))*((1<<(p+2))-3)+1;
}
template<class T,class F>
int operator()(T p1,T p2,F cmp)
{
for(int tp=upper_bound(a,a+15,p2-p1)-a-1;tp>=0;--tp)
for(int i=a[tp],tmp=a[tp];p1+i<p2;++i)
for(int j=i;j>=tmp&&cmp(*(p1+j),*(p1+j-tmp));j-=tmp)
swap(*(p1+j),*(p1+j-tmp));
return 0;
}
}shell_sort;
uint32_t arr[100010],n;
P1177()
{
n=read();
for(int i=1;i<=n;++i)
arr[i]=read();
shell_sort(arr+1,arr+n+1,less<int>());
for(int i=1;i<=n;++i)
printf("%u ",arr[i]);
}
}obj;
int main()
{
return 0;
}
本段代码测速
完结撒花✿✿ヽ(°▽°)ノ✿✿
了吗?
基于比较的排序算法的时间复杂度下界的证明
真·完结撒花✿✿ヽ(°▽°)ノ✿✿