Ubuntu20.04 Linux C/C++----插入排序&时间复杂度分析

#include <stdio.h>

#define LEN 5
int a[LEN] = { 10, 5, 2, 4, 7 };

void insertion_sort(void)
{
int i, j, key;
for (j = 1; j < LEN; j++) {
printf("%d, %d, %d, %d, %d\n",
a[0], a[1], a[2], a[3], a[4]);
key = a[j];
i = j - 1;
while (i >= 0 && a[i] > key) {
a[i+1] = a[i];
i–;
}
a[i+1] = key;
}
printf("%d, %d, %d, %d, %d\n",
a[0], a[1], a[2], a[3], a[4]);
}

int main(void)
{
insertion_sort();
return 0;
}
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

显然外层 for 循环的执行次数是n-1次,假设内层的 while 循环执行m次,则总的执行时间粗略估计是(n-1)×(c1+c2+c5+m×(c3+c4))。当然, for 和 while 后面()括号中的赋值和条件判断的执行也需要时间,而我没有设一个常数来表示,这不影响我们的粗略估计。

这里有一个问题,m不是个常数,也不取决于输入长度n,而是取决于具体的输入数据。在最好情况下,数组 a 的原始数据已经排好序了, while 循环一次也不执行,总的执行时间是(c1+c2+c5)×n-(c1+c2+c5),可以表示成an+b的形式,是n的线性函数(Linear Function)。那么在最坏情况(Worst Case)下又如何呢?所谓最坏情况是指数组 a 的原始数据正好是从大到小排好序的,请读者想一想为什么这是最坏情况,然后把上式中的m替换掉算一下执行时间是多少。

数组 a 的原始数据属于最好和最坏情况的都比较少见,如果原始数据是随机的,可称为平均情况(Average Case)。如果原始数据是随机的,那么每次循环将已排序的子序列 a[1…j-1] 与新插入的元素 key 相比较,子序列中平均都有一半的元素比 key 大而另一半比 key 小,请读者把上式中的m替换掉算一下执行时间是多少。最后的结论应该是:在最坏情况和平均情况下,总的执行时间都可以表示成an2+bn+c的形式,是n的二次函数(Quadratic Function)。

在分析算法的时间复杂度时,我们更关心最坏情况而不是最好情况,理由如下:

最坏情况给出了算法执行时间的上界,我们可以确信,无论给什么输入,算法的执行时间都不会超过这个上界,这样为比较和分析提供了便利。
对于某些算法,最坏情况是最常发生的情况,例如在数据库中查找某个信息的算法,最坏情况就是数据库中根本不存在该信息,都找遍了也没有,而某些应用场合经常要查找一个信息在数据库中存在不存在。

比较两个多项式a1n+b1 和a2n2+b2n+c2 的值(n取正整数)可以得出结论:n的最高次指数是最主要的决定因素,常数项、低次幂项和系数都是次要的。比如100n+1和n2+1,虽然后者的系数小,当n较小时前者的值较大,但是当n>100时,后者的值就远远大于前者了。如果同一个问题可以用两种算法解决,其中一种算法的时间复杂度是线性函数,另一种算法的时间复杂度是二次函数,当问题的输入长度n足够大时,前者明显优于后者。因此我们可以用一种更粗略的方式表示算法的时间复杂度,把系数和低次幂项都省去,线性函数记作Θ(n),二次函数记作Θ(n2)。

Θ(g(n))表示和g(n)同一量级的一类函数。比如g(n)=n2 ,所有的二次函数(例如f(n)=1/2n2-3n)都和g(n)属于同一量级,甚至有些不是二次函数的函数(例如f(n)=2n2+3lgn)也和g(n)属于同一量级,它们都可以用Θ(g(n))(即Θ(n2))来表示。“同一量级”这个概念可以用下图来说明(该图出自 [算法导论] 的3.1节):
在这里插入图片描述
如果可以找到两个正的常数c1 、 c2 和常数n0 ,使得n≥n0 (也就是n足够大)的时候f(n)总是夹在c1g(n)和c2g(n)之间,就说f(n)和g(n)是同一量级的,f(n)就可以用Θ(g(n))来表示。

以二次函数为例,比如1/2n2-3n,要证明它属于Θ(n2)这一类函数的集合,我们必须确定c1 、c2 和n0 ,这些常数不随n改变,并且当n≥n0 时c1n2≤1/2n2 -3n≤c2n2 总是成立的。为此我们从不等式的每一边都除以n2 ,得到c1≤1/2-3/n≤c2 。函数1/2-3/n的曲线见下图:
在这里插入图片描述

这样就很容易看出来,无论n取多少,该函数一定小于1/2,因此c2=1/2。当n=6时函数值为0,n>6时该函数都大于0,可以取n0=7,c1=1/14,这样当n≥n0 时都有1/2-3/n≥c1。通过这个证明过程可以得出结论,当n足够大时任何an2+bn+c都夹在c1n2 和c2n2 之间,相对于n2 项来说bn+c的影响可以忽略,a可以通过选取合适的c1 、c2 来补偿。

几种常见的时间复杂度函数按数量级从小到大的顺序依次是:Θ(lgn),Θ(sqrt(n)),Θ(n),Θ(nlgn),Θ(n2),Θ(n3),Θ(2n),Θ(n!)。其中,lgn通常表示以10为底n的对数,但是对于Θ-notation来说Θ(lgn)和Θ(log2n)并无区别(想一想这是为什么),因而在算法分析中lgn通常表示以2为底n的对数。可是什么算法的时间复杂度里会出现lgn呢?回顾插入排序的时间复杂度分析,无非是循环体的执行时间乘以循环次数,只有加和乘运算,怎么会出来lg呢?下一节归并排序的时间复杂度里面就有lg,请读者留心lg运算是从哪出来的。

除了Θ-notation之外,表示算法的时间复杂度常用的还有一种Big-O notation。我们知道插入排序在最坏情况和平均情况下时间复杂度是Θ(n2),在最好情况下是Θ(n)(数量级比Θ(n2)要小),那么总结起来在各种情况下插入排序的时间复杂度是O(n2)。Θ的含义和“等于”类似,而大O的含义和“小于等于”类似。

Bing:Linux C/C++编程一站式学习-----11. 排序与查找----插入排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值