插入排序
所谓插入排序,就是排序问题的一个算法.
排序问题
算法的本质,就从输入到输出的可靠流程.
输入:n个数的有穷序列
<
a
1
,
a
2
,
.
.
.
,
a
n
>
<a_1,a_2,...,a_n>
<a1,a2,...,an>
输出:输入序列的一个排列,
<
a
1
′
,
a
2
′
,
.
.
.
,
a
n
′
>
<a_1',a_2',...,a_n'>
<a1′,a2′,...,an′>,满足
∀
i
<
j
:
a
i
′
≤
a
j
′
\forall i<j:a_i'\le a_j'
∀i<j:ai′≤aj′
另外,在排序问题中,称呼需要排序的序列中的元素为关键词.
但作者没有使用过这个称呼.
直观描述
就好像一个人从牌堆抽取扑克牌的过程.
每次抽取到牌后,放入手中该牌应该处于的位置.
无论是在抽到牌前,还是抽到牌后,手牌均处于一个顺序正确的状态.
一直到抽完所有的牌,最终就能得到这些牌正确的顺序.
譬如,手中含有(3,4,5,10),再抽到7的时候,就应该夹到5,10之间.
C的描述
void insertion_sort(int *a,int n)
{
for(int j=2;j<=n;j++)
{
int key=a[j];
int i=j-1;
while(i>0&&a[i]>key)
{
a[i+1]=a[i];
i--;
}
a[i+1]=key;
}
}
分析
算法以循环的形式进行.
每次循环开始前,a[1,j-1]相当于已经在手中排好序的牌,而a[j,n]则为还在牌堆中的牌.
每次循环,抽取到的牌即为a[j].要在当次循环中确定好它的位置,并将其放入.
循环不变式
一个新的名词.
大家可以看到,循环中,给定的输入序列不断变换,最终得到满足要求的输出序列.
以序列(5,2,4,6,1,3)为例,粗体为当次循环中所要定序的牌,即抽到的牌.
524613,j=2
254613,j=3
245613,j=4
245613,j=5
124563,j=6
123456
循环不变式,即在循环中,不断变化的数据所满足的,某个性质,或者是命题.
在这里,循环不变式即为:每次循环开始前,a[1,j-1]的数据以及处于按序排列状态.
循环不变式有3条性质需要证明:
1.初始化.在循环开始前,命题为真.
2.保持.每次循环不会使本命题由真变假.
3.终止.算法结束后,循环不变式对我们得到要求的输出有所帮助.
就本例而言,初始化显然为真,j=2时a[1,1]只有一个元素.
保持,要说清并不轻松.
终止:显然j=n+1时结束,此时不变式确保了a[1,n]的元素已被排好了序,因此我们才能指出这就是我们要的输出序列.
算法分析
作者简略介绍了很多RAM模型的分析法,但因为过于复杂而放弃了.
输入规模
算法需要的时间,与算法输入的规模同步增长,一般来说,至少是正比例增长.
因此,通常用带有输入规模n的函数来描述算法的运行时间的大概.
但输入规模的定义依赖于问题本身.例如排序问题中,就适合以元素的数目为输入规模,和一张图有关的问题,规模就应该考虑图的节点数和边数.
因此,是一个随问题而灵活定义的东西.
运行时间
即算法需要执行的基本操作的步数的时间,在输入规模确定的情况下.
作者假设代码中,每行运行的时长均为固定的常量 c i c_i ci(第i行,共8行).
那么只需要再确定每行代码运行的次数,即可确定最终的时间了.
令输入规模为n时,算法的运行时间为T(n),每行代码运行的次数为 t i t_i ti,则有
T ( n ) = ∑ i = 1 8 t i c i T(n)=\sum_{i=1}^8 t_ic_i T(n)=∑i=18tici
分析实例
作者进行了很仔细的分析,略.
总之,输入规模定为序列的长度n.
如果输入序列一开始就排好了序,那么 T ( n ) T(n) T(n)会取到最小值,这是最好的情况.
大概类似 T ( n ) = ( c 1 + c 2 + c 4 + c 5 + c 8 ) n − ( c 2 + c 4 + c 5 + c 8 ) T(n)=(c_1+c_2+c_4+c_5+c_8)n-(c_2+c_4+c_5+c_8) T(n)=(c1+c2+c4+c5+c8)n−(c2+c4+c5+c8)
显然, T ( n ) T(n) T(n)是关于n的线性函数.
如果输入序列是反向排序的,即最糟情况,那么 T ( n ) T(n) T(n)会得到最大值.
一个很复杂的式子,会得到 T ( n ) T(n) T(n)是关于n的二次函数.
在这里,作者指出,算衡量运行时间的好坏,并非是看最好情况和最坏情况如何折中,而是仅仅看最差时间.
因为很多情况中,非最好情况的时间和最坏情况没什么区别.
增长量级
相比具体的运行时间,更值得关注的是,算法如何随输入规模的增长而增长.
为此,在公式T(n)中,进一步忽视掉每条语句的常量
c
i
c_i
ci,和n的非最高阶项.这样得到的纯粹的n的最高次项.
这样,我们就可以得到最坏情况下运行时间可以表示为
θ
(
n
2
)
\theta(n^2)
θ(n2).
不同的增长量级,在n的规模足够大的情况下,总能进行绝对的比较.