173-算法效率分析

健壮性指的是什么?
健壮性是指程序在运行过程中出现一般性的错误,程序会自动进行错误处理函数。
可靠性是指程序在运行过程中出现错误的概率,一般会做一些可靠性试验来测试MTBF(平均无故障工作时间)

时间复杂度

函数功能不仅仅要写出来,提高健壮性,同时还要讲究效率的问题。
算法的效率是评价一个算法优劣非常重要的指标。那么算法的效率主要分为时间复杂度和空间复杂度两个部分。

时间复杂度:算法中基本操作执行的次数和问题规模 n 之间的函数关系。记 作 O(f(n))。(O 是大写字母 o,不是数字零)

常见循环时间复杂度

例 1.计算下面代码的时间复杂度

for(i=1; i<=n; ++i)
tmp += arr[i];

主要计算”tmp += arr[i];”这条语句的执行次数。
i 从 1 到 n 一共执行 n 次,每一次都会进入循环执行”tmp += arr[i];”这
条语句,即这条语句一共执行了 n 次,所以函数关系为 f(n) = n。那么时间复杂度为 O(f(n))=O(n);

例 2.计算下面代码的时间复杂度

for(i=1;i<=n; ++i)
    for(j=1; j<=n; ++j)
        { c[i][j]=0 ; }//1.基本语句

基本语句的执行次数:
当 i 是 1 的时候,j 可以从 1 到 n,那么基本语句执行 n 次。
当 i 是 2 的时候,j 可以从 1 到 n,那么基本语句执行 n 次。

当 i 是 n 的时候,j 可以从 1 到 n,那么基本语句执行 n 次。
那么总共执行的次数是 n+n+n+…+n=n^2。
所以时间复杂度为 O(f(n))=O(n^2)。

例 3.计算下面代码的时间复杂度

 {++x; s=0 ;}//1.基本语句

上面基本语句没有循环,和 n 没有关系,它就是两条语句,也就意味着随着问题规模的增大,语句执行的次数一直是个常数 2。这种情况的时间复杂度是 O(1)。
O(1):执行次数是个常数和问题规模没有关系,这个常数可以是 1,100,1000等。

例 4.计算下面代码的时间复杂度

 for(i=1; i<=n; ++i)
    { ++x; s+=x ; } //1.基本语句

基本语句”++x;s+=x;”一共有两条语句,总次数是 2*n,那么时间复杂度是多少呢?
由于计算时间复杂度主要是考察问题规模和执行次数增长率的问题,为了简单起见,不考虑系数。所以 O(f(n))=O(n)。

例 5. 计算下面代码的时间复杂度

for(i=2;i<=n;++i)
   for(j=2;j<=i-1;++j)
      {++x; a[i][j]=x; }//基本语句

这次基本语句执行的总次数不容易得到,那么就一条一条计算。
当 i 是 2 的时候,j 执行 0 次,基本语句整体执行 0 次
当 i 是 3 的时候,j 执行 1 次,基本语句整体执行 1 次。
当 i 是 4 的时候,j 执行 2 次,基本语句整体执行 2 次。
当 i 是 5 的时候,j 执行 3 次,基本语句整体执行 3 次。

当 i 是 n 的时候,j 执行 n-2 次,基本语句整体执行 n-2 次。
那么执行的总次数是:
f(n)=2*(0+1+2+3+…+n-2)=2*(0+n-2)*(n-1)/2=n^2-3n+2。
随着问题规模的增大,n^2 才是影响最大起决定作用的,时间复杂度并不是精确的计算,所以只抓主要矛盾,只保留高阶项,即 O(f(n))=O(n^2)。

算法复杂度计算总结:

1.只保留高阶项;2.不要系数。

例 6. 计算下面代码的时间复杂度

for(int i=1;i<n;i*=2)
    { ++x; }//基本语句

注意这个不是 i++,而是 i*=2
i 的遍历轨迹是:1,2,4,8…,m。(m 是大于等于 n 的一个数字),而这个遍历的次数就是基本语句执行的次数。
在这里插入图片描述
1,2,4,8…每次都是在原有的基础上乘以 2,假设遍历的次数为 x,那么
就是 2^x >= n,边界也就是±1,故忽略不计,则表达式为 2^x = n,推出 x = log2 𝑛。之后为了简单书写将log2 𝑛简写为 logn。
时间复杂度为 O(f(n))=O(logn)。

上面的都是一些循环算法计算,比较容易理解。那如果是递归算法呢?

递归算法时间复杂度

例 7.下面代码的算法时间复杂度

int Fun1(int n)
{
    if(n <= 1) return n;
    else return Fun1(n-1)+1; //递归
}

表面上看这个的时间复杂度像是 O(1),但实际上当然不是。为什么呢?因为Fun1(n)会调用 Fun1(n-1),再调用 Fun1(n-2),Fun1(n-3),…,Fun1(1)。函数调用也是需要时间的,假设函数调用的时间为常数 c,那么这么多次函数调用的总时间为 c*n;c 为系数可以忽略,那么时间复杂度为 O(f(n)) = O(n)。

例 8.下面代码的算法时间复杂度

int Fun2(int n)
{
    if(n <= 1) return n;
    else return Fun2(n-2)+2;
 }

函数调用的关系为 Fun2(n)调用 Fun2(n-2),Fun2(n-4),…,Fun2(1)或 者 Fun2(0),总调用次数为 n/2 次,所以时间复杂度为 O(f(n))=O(n)。

例 9.下面代码的算法时间复杂度

int Fun3(int n)
{
    if(n <= 1) return n;
    else return Fun3(n-10)+1;
}

函数调用关系为:
Fun3(n),Fun3(n-10),Fun3(n-20),Fun3(n-30)…Fun3(0)或者 Fun3(小 于 0),总调用次数为 n/10,所以时间复杂度为 O(f(n))=O(n)。

例 10.下面代码的算法时间复杂度

int Fun4(int n)
{
    if(n <= 1) return n;
    else return Fun4(n/2)+1;
}

O(f(n))=O(logn)

空间复杂度

空间复杂度:实现这个算法所需要的额外的辅助空间和问题规模 n 之间的函数关系。

这里不看执行的次数,而是实现该算法,需要的额外的辅助空间的问题。记作
O(f(n))。
常见函数空间复杂度

例 1.计算下面算法的空间复杂度

 for(int i=1;i<n;i*=2)
    { ++x; }

实现该算法需要的辅助变量一个是 i,一个是 x。当问题规模 n 是 100 时只需要这两个变量,当问题规模 n 是 10000 时还是只需要这两个变量。辅助空间为常数和问题规模没有关系,所以空间复杂度 O(f(n))=O(1)。

例 2.计算下面算法的空间复杂度

//筛选法求素数
void Fun(int n)
{
	int *arr = (int *)malloc(n*sizeof(int));//动态开辟内存做标记
	int i;
	for(i=0;i<n;i++)//假设所有的数字都是素数
	{
		arr[i] = 1;
	}
	/*
	将不是素数的数字剔除
	......
	输出所有的素数
	......
	*/
	free(arr);
}

上面代码是节选筛选法求素数的部分代码,它需要开辟 n 个长度的数组做标记,当 n 为 10 时标记数组需要开辟 10 个长度,当 n 为 100 时需要开辟 100 个长度,所以这个算法的空间复杂度为 O(f(n))=O(n)

递归的空间复杂度

例 3.计算下面算法的空间复杂度

int Fun(int n)
{
    if(n <= 1) return n;
    else return Fun(n-1)+1; //递归
}

递归的空间复杂度和时间复杂度一样不太容易看出来,需要简单的计算。
函数调用需要利用栈空间来传递参数和保存局部变量的数据,把一次函数调用的空间称为一个栈帧,假设一个栈帧的大小为 c。
递归函数调用的次数较多,不达到边界条件(上面为 n<=1),函数不能退出即在栈中占用的内存也不能释放,这也是算法消耗的辅助空间。
本例中,函数调用为 Fun(n),Fun(n-1)…Fun(1),总调用次数为 n 次,每
次占用的空间为 c,所以空间复杂度为 O(f(n))=O(n)
在这里插入图片描述
递归算法代码一般比较简洁,但是其算法的时间复杂度和空间复杂度一般较大

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值