C++算法:了解算法的复杂度


前言

有计算机科学家说过 “程序=数据结构+算法”,前面学习了基本的数据结构和树形、图结构,那么就可以开始算法学习了。算法是一个程序员必须掌握的指导工具,算法可以认为是解决问题的具体方法步骤。那么怎么描述一个算法的优劣就成了最先要了解的事了。


一、算法是什么?

在计算机中,算法可以认为是一段逻辑清晰的系列指令集合。这段指令集合应该具备以下基本特征:

1、有限的运算,我们必须让代码在一个合理的时间段内解决问题,每一个语句的运算时间也是有穷的。就像计算1至100的和,对计算机来说1+2+3 ... +99+100是有限的运算,大约做一百次加法。

2、可行性,对上述加法来说,100次加法是一个可以接受的计算方法,这个算法和 (1+100)*(100/2) 一样都是可以得到正确结果并耗时基本一致的。算法的任何一步计算都是可以通过计算机基本运算(加减乘除逻辑位移等运算)实现的。

3、确定性,对于同样的输入应该能得到同样的输出,每个指令都不能产生二义。

4、输入输出,这段代码接受一个或多个输入,同样它会产生一个或多个输出结果。

满足以上四个条件的,用代码可以表示出来的,用于描述解决问题的步骤就可以称为是一个算法。那么对于计算机来说,解决一个同样的问题,就可能存在很多个解决算法。同样对上述1至100的加法来说,二种计算方法都是可行的,在编译以后所费的时间也是差不多的。那么这两个算法是一样好的算法吗?很显然它们之间是有优劣之分的,小学老师就告诉我们了,高斯同学的算法更好!对计算机来说也是一样的。这就是算法复杂度所要描述的事情。

二、算法复杂度是什么

众所周知的,计算机处理器的速度是不同的。所以我们无法用每一条基本指令的运行时间去衡量计算机处理问题的速度。那么我们只能统计有多少个基本计算用于表示问题处理的速度。
如上述的加法运算,我们可以写成如下代码:


#include <iostream>

using namespace std;

int main(){
    typedef long long int ll;
    ll sum = 0;
    for (ll i=1; i<=100; i++){
        sum += i;
    }
    cout << "sum = " << sum << endl;
    return 0;
}

显然它有100次加法运算、100次给sum赋值、i 自增100次、i 还有100次比较是否大于100、i 100次赋值的运算,还有存储器读取等操作。我们无需描述每一个cpu基本运算步骤,那太复杂了,也很难对每一个算法如此精确描述。我们可以像数学的导数描述一个函数的变化规律一样,用函数增长率来描述算法的复杂度。这个加法运算中,我们只需要关心运行时间是如何随 n(对上述加法而言是100)这个数据的大小规模增长而增长的。对于加到100来说,我们就认为它的时间复杂度是O(100),可以简单的认为它计算了100次(实际上不是)。那么对于这个逐个相加的算法就可以认为它的时间复杂度是O(n)。有关于时间的描述自然也有关于空间(内存占用)的描述,上述算法可以描述为O(1)的空间复杂度,虽然它有二个变量 i 、sum 和一个常量100。但基于同样的理由,我们只关心空间占用和 n 增长的关系,n 增加到10000也还是二个变量和一个常量。

因为内存在这个年代不值钱了,只要不是太离谱,程序员们就不太关心空间复杂度(嵌入式开发者会同时关心空间和时间复杂度)。更多的以时间复杂度来描述一个算法的复杂度。

那么这个描述有意义吗?当然是有的,如果我们把上述加法的 n 换成1亿,1加到1亿,它的算法时间复杂度是O(n)。就简单的理解成1亿次吧(实际上不是)。对于现代计算机来说,1亿这个数也不是不能接受,大约在5秒钟左右它就算出来了,但这已经比很多人算得慢了。

那么高斯算法呢?我们知道高斯算法就一公式,按上述复杂度理论,它的计算时间复杂度可以描述成O(1),显然这是一个常数,我们可以理解成一次就计算出来了。

int main(){
    typedef long long int ll;
    ll sum = (ll)(1+1000000000)*(1000000000/2);
    cout << "sum = " << sum << endl;
    return 0;
}

实际上他和计算 (1+100)*(100/2) 所花的时间也没有本质的区别,基本上是一样以毫秒计的。

综上所述,O(1) 时间复杂度的算法是远优于O(n)时间复杂度的算法的。那么时间复杂度的描述就在我们解决实际问题时产生了指导作用!

显然高斯算法和 n 的数据大小规模无关,是一个常数。而逐个相加的算法,是随着 n 的数据规模变大而变得更复杂耗时的。在 n=100 时,这两个算法几乎没有耗时上的区别。随着 n 大小规模变大,O(n) 算法的复杂度是呈线性增长的,O(1) 复杂度的算法是不变的。
当 n 趋向于无穷大时,只要计算机能处理,高斯算法也是能解决这个问题的,而逐个相加的算法显然是无法解决的,实际上目前主流个人计算机大概也就能加到1E10左右,当然对于C++来说从1加到1E10也要内存溢出了。
由此可以得出一些显而易见的算法的复杂度,如:

二个 for (均为1至n)循环嵌套的算法时间复杂度就是O(n2)。
二分查找和笔者数据结构中描述的二叉搜索树(平均情况)的时间复杂度就是O(logN)。
以此类推,很多算法的时间复杂度都是一眼即明的。当然也有些算法的时间复杂度是很难准确描述的。上述二叉搜索树就存在多个情况,这和二叉搜索树生成的结构有关,存在最优情况、最坏情况(O(n)),我们一般就以平均情况描述。

顺便说一下笔者在小议递归中曾描述过的递归计算斐波那契数列的时间复杂度其实是O(2n),所以很不可行。


总结

通过一个简单的例子,我们理解了时间复杂度的描述,这对我们在实际解决问题时根据不同的数据规模选择不同的算法是很有用的。

原创文章,未经许可,严禁转载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

无证的攻城狮

如本文对您有用,大爷给打个赏!

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

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

打赏作者

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

抵扣说明:

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

余额充值