算法的时间复杂度详解 一名程序员应该了解的算法知识

这两天看了一下《大话数据结构》中的算法时间复杂度,感觉还是挺详细,对算法是时间复杂度有了一个更深刻的了解,为了加深对算法的时间复杂度的记忆和理解,同时也和大家分享我对算法时间复杂度的理解,如果有问题,希望能够多多指点交流。

函数的渐进增长

现在有两个算法:算法A(2n+3)和算法B(3n+1),你觉得这两个算法那个更快一些呢?

准备来说,答案是不一定的(如下表)

次数算法A(2n+3)算法A‘(2n)算法B(3n+1)算法B’(3n)
n=15243
n=27476
n=396109
n=1023203130
n=100203200301300

在n=1时,算法B效率更高,因为算法B次数更少。当n=2时,两者效率相同;当n>2时,算法A就开始优于算法B,并且随着n的增加,算法A效率越来越高(执行的次数比B要少)。因此这时算法A比算法B要更好。

因此在输入规模n在没有限制的情况下,只要超过一个数值N,这个函数就总是大于另一个函数,我们称函数是渐进增长1的。同时可以发现后面的+3和+1其实对算法的影响并不大,因此,我们可以忽略这些加法常数

同样,请看算法C(4n+8)和算法D(2n2+1),你觉得哪个算法更好呢?

次数算法C(4n+8)算法C’(2n)算法D(2n2+1)算法D’(2n2)
n=112131
n=216294
n=3203199
n=104810201100
n=100408100200011000

当n<=3时算法C的没有D好,因为C次数比较多,但当n>3,算法C的优势就越来越明显,n越大两算法的差距则越大。而当去掉后面的常数后,其实对结果也没有改变。就算去掉与n相乘的常数,结果也没有发生改变,C‘的次数随着n的增长,还是远小于算法D’。因此,与最高次项相乘的常数并不重要。

第三个例子。算法E(2n2+3n+1),算法F(2n3+3n+1)。

次数算法E(2n2+3n+1)算法E‘(n2算法F(2n3+3n+1)算法F’(n3)
n=16161
n=2154238
n=32896427
n=1023110020311000
n=10020301100020003011000000

n=1时两个算法的结果相同,当n>1后,算法E的优势就要开始优于算法F,且随着n的增大差距非常明显。通过观察发现,最高次项的指数越大,函数随着n的增长,结果也会越来越大。

最后一个例子。算法G(2n2),算法H(3n+1),算法I(2n2+3n+1)

次数算法G(2n2算法H(3n+1)算法I(2n2+3n+1)
n=1246
n=28715
n=3501666
n=1020031231
n=1002000030120301
n=100020000003001200301
n=100002000000003000120003001

通过这三个算法可以清楚的看到。当n的值越来越大时,3n+1已经和另外两个算法结果相比较了,最终机会可以忽略不计。也就是说,随着n值变的非常大以后算法G其实已经很趋近与算法I。于是我们可以得到这样的一个结论——判断一个算法的效率时,函数中的常数和其它次项要项常常可以忽略,而更应该关注最高次项的项数。

因此判断一个算法效率时,不能只通过少数数据来判断。通过上面的例子我们可以发现,我们可以通过对比这几个算法的关键执行次数函数的渐近增长性,来得出一个算法随着n的增大,它会越来越优于另一个算法,或者越来越劣于另一个算法。通过算法的时间复杂度来估算一个算法的时间效率。

算法时间复杂度

  • 算法时间复杂度的定义

算法时间复杂度定义:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n) =O(f(n))。它代表随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。

一般情况下,随着n的增大,T(n)的增长最慢的算法为最优算法

显然,由此算法时间复杂度的定义可知,从上面几个例子中,用到了常数阶O(1)、线性阶O(n)、平方阶(n2),当然还有一些其他的阶,后面会和大家列举。

  • 推导大O阶的方法
    • 推导大O阶
    1. 用常数1取代运行时间中的所有加法常数。

    2. 在修改后的运行次数函数中,只保留最高阶项。

    3. 如果最高阶项存在且不是1,则取出与这个项相乘的常数。

      得到的结果就是大O阶,之所以用这样得出的原理,从上面的函数渐进增长就能总结出。

  • 常见的时间复杂度
    • 常数阶
      int sum =0,n=100;	/*执行一次*/
      sun = (1+n)*n/2;	/*执行一次*/
      printf("%d",sum);	/*执行一次*/
      

      这个算法的运行次数函数是f(n)=3。根据上面推导方法,将常数项3改为1。因为没有最高阶项,因此这个算法的时间复杂度为O(1)。

      同时可以发现无论n为多少,代码之间的差异仅仅只是执行次数的差异。这种与问题大小无关(n的多少),执行时间恒定的算法,称为O(1)时间复杂度,又叫常数阶。

      无论常数是多少,都记作O(1),而非O(3)、O(7)等其它数字。而且单纯的分支结构(不包含循环结构中),其时间复杂度也是O(1)。

    • 线性阶

      要确定某个算法的阶次,常常需要确定某个特点语句或语句集运行的次数。因此,我们要分析算法的复杂度,关键就是要分析结构的运行情况。

      int i;
      for (i = 0;i<n;i++){
          /*时间复杂度为O(1)的程序步骤序列*/
      }
      

      因为循环体中的代码要执行n次。所以实践复杂度为O(n)。

    • 对数阶
      int count = 1;
      while (count < n){
      	count = count * 2;
      	/*时间复杂度为O(1)的程序步骤序列*/
      }
      

      由于每次count乘以2之后,就距离n更近了一分。也就是说,有多少个2相乘后大于n,则会退出循环。由2x=n得到x= log2n。所以这个循环的时间复杂度为O( logn)。

    • 平方阶
      int i,j;
      for(i = 0; i < n; i++){
          for(j = 0; j < n; j++){
              /*时间复杂度为O(1)的程序步骤序列*/
          }
      }
      

      一个循环的时间复杂度为O(n),如果还嵌套一个循环就是循环的平方。所以这段代码的时间复杂度为O(n2)。

      通过上面这些例子可以得出,其实理解大O推导并不算难,难的是对数列的一些相关运算,这更多的是考察你的数学知识能力和逻辑思维能力。因此一名合格的程序员,不能忽视数学知识。

    • 常用时间复杂度及其耗费时间大小排序
    执行次数函数非正式术语
    12O(1)常数阶
    2n+3O(n)x
    3n2+2n+1O(n2)平方阶
    5log2n+20I对数阶
    2n+3nlog2n+19O(nlogn)nlogn阶
    6n3+2n2+3n+4O(n3)立方阶
    2nO(2n)指数阶

    O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)

本文部分内容摘自《大话数据结构》清华大学出版社

2020/4/26

  1. 函数的渐进增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么,我们就说f(n)的增长渐进快于g(n) ↩︎

  • 12
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值