zoukankan      html  css  js  c++  java
  • 浅入浅出数据结构(11)——简要介绍算法时间复杂度

      在接下来的数据结构博文中,我们将会开始接触到一些算法,也就是“解决某个问题的方法”,而解决同一个问题总是会存在不同的算法,所以我们需要在不同的算法之中做出抉择,而做出抉择的根据往往就是算法耗费的时间(特殊情形下我们还需要考虑算法耗费的空间)。因此我们今天就来学习如何简单的判断算法将会耗费的时间。

     

      首先我们知道,同一个算法或者说同一个程序,在不同的计算机上耗费的时间是不一样的,因为不同的计算机硬件运算能力会存在不同。此外,在同一台计算机中,不同的操作,如加法和乘法要消耗的时间也会不同,整数加法和浮点数加法消耗的时间也会不同,因为同样在高级编程语言中只需要一个语句的操作,在计算机语言层面上对应的指令数量、计算机完成的速度也会不同。

      但是因为我们希望讨论的是“算法将要耗费的时间”,而不是完全具体情况下程序将要耗费的时间,所以我们计算算法将要花费的时间时,做不到或者说不应该计算出它具体的时间花费(比如xxx秒)。因此我们计算算法的时间花费时很重要的一点就是:不考虑某个具体操作要消耗的时间,我们将“一个操作”花费的时间统一记为“一个时间单位”,不论这个操作是a=b+c还是a+=b*c。

     

     

      接下来我们要明白一个东西,俗称“大O阶”。在讨论大O阶之前,我们要明白一点:我们计算算法将花费的时间时,一般都是计算其可能花费的最大时间。为什么呢?我们假设我们要解决的问题是查找或者排序,那么不论什么算法,在最良好的情况下都可以出现“一查就恰好找到”和“数据本身已经排好序”的情况,而这样作比较是不能比较出算法的“优劣”的,我们希望看到的,就是在最坏的情况下各个算法能够做出怎样的速度。所以我们计算算法消耗的时间,一般都是计算其最坏情况(也有可能计算其平均情况,因为最坏情况下速度不太良好的算法有可能平均情况下是够好的)。

     

     

      现在,我们假设这样一个算法:用顺序比较(从第一个开始逐一比较)的方法,在一个大小为n的数组中查找指定数据,若存在则返回true,否则返回false。这个算法的最坏情形显然是不存在指定数据或指定数据为数组最后一个元素,此时算法将执行n次比较操作,也就是算法将耗费n个时间单位。这样的表述当然没有问题,但我们希望能够更简洁的表达这个意思,该怎么办呢?这时候就需要大O阶出场了。

      不难发现,上述算法的最坏情形并不是“固定”的它取决于n的大小,可以说是一个与n相关的函数,因此我们可以考虑将其记为函数形式:O(n)(基本上算法的最坏情形都不是固定的,而是与数据量有关。因为我们说过算法是处理数据的,如果不论数据多少,执行的操作都是一样多,那这部分操作是不是个算法都不好说)。即O(n)代表着算法在最坏情形下耗费的时间单位,而这个“O()”就是我们所说的大O阶(并不严谨,需继续向下看)。

     

      知道了我们该计算算法的最坏情形以及什么是大O阶之后,我们现在来试试计算下面这个“算法”的大O阶(关于如何计算一段程序耗费的时间在本文最后简介)

     

    /*从键盘或文件获取n个数据,获取单个数据需要3个操作*/
    
    
    /*对所有数据进行操作,某些数据的操作为3个,某些数据的操作为4个*/
    
    
    /*输出或存储所有数据,单个数据需要3个操作*/
    /*执行100个额外操作*/

      根据我们之前所说的,任意一个操作(除非这个操作本身是调用一个函数,且该函数花费的时间也与数据有关)均假设花费1个时间单位,上述算法累计有3*n+3*(n-x)+4*x+3*n+100个操作,简单化简的话就是9*n+x+100,由于对数据操作时存在区别对待,所以存在一个不确定的x。

      那么问题来了,我们这时候应该说该算法的大O阶为O(9*n+x+100)吗?显然是不应该的,因为如果要将这样的未知量也算上,也就意味着对于算法中的每个选择结构都得设置单独的未知量,当算法的选择结构很多时,计算算法的时间花费就会很困难。那么对于这样的“未知量”我们该如何处理?很简单,既然大O阶要代表算法的最坏情况,那我们就假设选择结构永远是最坏情况,在上述算法中也就是永远都是对数据执行4个操作而不是3个。这样一来,大O阶的计算就简单了一些,3*n+4*n+3*n+100=10n,为O(10*n+100)。

      现在我们再来看看大O阶中的常数项,乍一看,我们会认为常数项也是计算算法时间花费时必不可少的,可是如果我们仔细想想我们分析算法时间花费的根本目的,就会发现,我们在乎的其实是算法时间花费与数据量n之间的关系。就像前面说的,如果一段程序不论数据量多少都是花费一样多的时间单位,那么这段程序算不算算法都是一个问题。出于这个原因,对于大O阶中的常数项,我们总是选择直接舍弃(不论大小),因此O(10*n+100)又被我们简化为了O(10*n)

     

     

      接下来我们再来看看这段“算法”,试着计算它要花费的时间

    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j)
            /*执行1次操作*/

      不难计算出其耗费的时间单位为n*n即n^2,大O阶为O(n^2)。通过对比我们可以发现,不论O(a*n)的系数a有多大,当数据量n大到一定程度之后,O(n^2)总是比O(a*n)要花费更多时间,简单地说就是n^2的增长率比a*n的要大得多。出于这个原因(以及之前说过的,常数项总是不可避免或者说不是重点考虑的内容),我们对于大O阶中的常数系数也采用省略的做法。所以之前我们计算出的O(10*n)在一般情况下我们也就简记为O(n)。

     

      讲到这儿,想必大家已经能够猜出我们计算大O阶最重要的一点是什么了,那就是“化简”,尽可能的化简:

      1.去掉常数项

      2.去掉常数系数

      3.只保留最高次项

     

      因此,假设一个算法消耗时间单位为3*n^3+n^2+9*n+239,我们也将其最坏情形表示为O(n^3)。显然的,这时候的大O阶已经不能较为精确的反映出算法要消耗的时间了,只能说反映出了算法时间“所处的等级”。但是由于在数据量n足够大的时候,“低等级”(如n^2)的算法总是会比“高等级”(如n^3)的算法更快,所以大部分时候我们也只需要在意算法耗费时间“所在的等级”,这也是我们极度精简时间单位的原因。

      同样的,由于极度精简了时间单位,所以大O阶我们也不再称之为“最坏情形下算法花费的时间”了,而是称之为算法的“时间复杂度”。

     

      那么,常见的算法都有哪些时间复杂度呢?一般按从快到慢有这么一些:

      O(1)(表示常数时间内完成,与数据量无关),O(logN)(底数不重要,一般为2),O(N)O(N*logN)O(N^2)O(N^3)

      我们在日后有可能会见识到相应的算法。

     

      

     

      最后,是关于计算一段代码耗费的时间单位时的一般法则:

      1.循环语句

      一次循环的运行时间最多是该循环内语句的运行时间乘以迭代的次数,如

     

    for(int i=0;i<n;++i)
    {
         a[i]=b[i];
         b[i]=b[i]+1;            
    }

      其运行时间为2*n,时间复杂度为O(n)

      2.嵌套的循环

      首先记住,从里向外分析循环。嵌套循环总的运行时间为最内部循环语句的运行时间乘以该组所有循环大小的乘积,如

    for(int i=0;i<n;++i)
         for(int j=0;j<n;++j)
              a[i]+=b[j];

      其运行时间为1*n*n,时间复杂度为O(n^2)

      3.顺序语句

      将各个语句的运行时间求和即可,如

    for(int i=0;i<n;++i)
        a[i]=i;
    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j)
            b[j]+=a[i];

      其运行时间为n+1*n*n,时间复杂度为O(n^2)

      4.选择语句

      选择语句的运行时间总是不会超过最长运行时间的那种可能,如

    if(Condition)
        S1
    else
        S2

      其运行时间总是不会超过S1,S2中的最大者加上判断语句的时间

     

      上述做法很可能使计算出的运行时间偏高,但可以保证算法不会超过这样的“最坏情况”。

     

     

      好了,这篇博文我们就讲这些,因为根据不同的需要,我们对程序的时间复杂度计算也会有不同的要求,而且像递归这样的代码,对其进行时间复杂度分析有时候会有很大困难,所以对于算法时间复杂度的简介,到这里就差不多了。

     

  • 相关阅读:
    文件管理系统(JQuery插件+Ajax)
    十大Ajax框架
    WSS3.0开发你还在为写CAML痛苦吗?
    vue获取微博授权的URL
    微博三方登录原理
    阿里云短信服务
    JWT原理和COOKIE原理
    django数据库的ORM操作
    celery原理与组件
    生成微博授权URL
  • 原文地址:https://www.cnblogs.com/mm93/p/7274670.html
Copyright © 2011-2022 走看看