首页 > 代码库 > 第二篇 算法概述及复杂度

第二篇 算法概述及复杂度

  虽然本系列随笔是记录数据结构相关的内容,但是我们都知晓算法和数据结构是密不可分的。我们也可以时常看到一个公式“程序设计=数据结构+算法”。数据结构是研究数据之前的关系,以及数据在计算机中的存储形式,而算法是让数据通过一定的形式得到我们需要的结果。这其中包含各种的逻辑运算等。

  提及算法,我们会自然的联想到一个关于数学牛人的故事:“高斯7岁那年开始上学。10岁的时候,一天,老师布置了一道题,1+2+3······这样从1一直加到100等于多少。高斯很快就算出了答案,起初高斯的老师布特纳并不相信高斯算出了正确答案:"你一定是算错了,回去再算算。”高斯说出答案就是5050,高斯是这样算的1+100=101,2+99=101······1加到100有50组这样的数,所以50X101=5050。“

  从上面的故事中我们可以得到以下几点:解决一个问题,往往可以采用的方式很多,第一种方式我们可以采用最原始的,从1一直加到100,第二中方式,(0+1+2+3+4+5+6+7+8+9)*10+100,可以理解为从1--100个位数先加,然后乘以十位数,第三种方式也就是高斯同学所采用的这种方式。除此之外还有许许多多可以运用的解法,而这种解法在数据结构中我们称为算法。同时我们在这种故事中也能看出算法的一些特性,首先,要具备正确性,其次算法要做到尽可能的优。

  首先给出算法的定义:算法是解决特定问噩求解步‘的描述,在计算机中表现为指令的有限序列,并且4事务指令亵示一个或多个操作。、

 

  算法的特性

    输入输出:任何算法都必须有零个或多个输入,但输出一定为大于等于一个。没有输出的算法是没有存在的必要,因为算法的本质就是经过某种处理得到预想的结果,只          是输出的方法可能不能。亦或是控制台,或者输出可见的文件资源等。

    有穷性:算法的执行必须要在有限的步骤中完成,同时在有限的时间里完成,步骤可以是千,万,甚至百万、亿,但是一定是有限的步骤来完成。而且时间的维度也是确

         定的。

    确定性:在同一个输入的情况下,输出的结果一定是确定的,同时是唯一的。不能存在二义性。

    可行性:算法在执行过程中,每一步都是可以通过有限的步骤来完成。

 

  算法设计的要求

   正确性:是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求、能够得到问题的正确答案。这也是算法的最基本要求,如果一个算法连正确性都        无法保证。我们不能称之为算法。

     可读性:算法的设计其中有个很重要的目的是便于传阅和理解、交流。如果一个算法写的晦涩难懂,久而久而理解这个算法的人会越来越少,最终其价值也会大大折扣。

     健壮性:针对不能输入,得到的结果都是预想中的结果,而不会出现莫名其妙的结果。但是有时候我们很难做到面面俱到,因此在设计算法的时候,要考虑的更多,包括

         其各种临界点等。

     时间效率高存储率低:满足了上面的三点要求,并不能说明该算法就是一个好的算法,一个好的算法应该尽量的追求时间效率高和存储率低。而这两种有时候很难做到完        全的契合。因此在实际的开发过程中我们可能会根据实际应用场景来进行权衡。有时候时间效率高而牺牲一部分空间存储,反之则通过牺牲时间来换的空间存储         率。

 

  算法的度量方法

   事后统计方法:主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。其实这种做法即我        们日常生活中所说的“事后诸葛亮”。这显然不是一种好的做法。有时候可能因为一种恶劣的算法,而浪费大量的时间。

   事前分析估算法:在编写算法之前,先对算法使用合理的分析对其进行评估,从而择优选择。在分析时我们往往考虑的方面如:算法采用的策略、编译产生的代码质        量、问题的输入规模、机器执行指令的速度。

 

  函数的渐近增长

    由于输入规模的不同,我们很难准确的说一个算法比另一个算法优,比如说:10n+100与n*n,在输入规模较小的时候(<17),n*n很明显更加的优秀,但是当值大      于17后,n*n将大于10n+100,而且后面的值始终大于前面。而值17其实是一个临界值。

    函数渐近增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大。我们此时称f(n)的增长渐近快于g(n)。

    如图:

    我们一般在算法编写或设计时,都会参照这样的方式来判断一个算法是否优越。

 

  算法的时间复杂度

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

 

    我们使用O()来体现算法的时间复杂度,简称为大O记法。

 

    推导大O阶方法

    1. 用常数1取代运行时间中的所有加法常数;
    2. 在修改后的运行次数函数中,只保留最高阶项;
    3. 如果最高阶项存在且不为1,则去除与这个项相乘的常数,得到的结果就是大O阶。

      上面的推导步骤,只能提供一定的指引,在实际分析过程中往往会异常的复杂。

   

    常见的算法复杂度

    常数阶O(1)、线性阶O(n)、对数阶O(logn)、平方阶(n*n)、立方阶等...

    

     举例说明:

     示例1:如果算法的执行时间不随着问题规模n的增加而增长,即使算法中有上千条语句,其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。

           int sum=0; int max=1000;
           for(int i=0;i<max; i++)
            {
              sum=sum+i;
            }
           解答: T(n)=O(1)
           推导步骤:在不考虑for循环条件中的计算次数,运行过程中共执行了一千次,即T(n)=1000,用常数1取代所有的加法常数,得到的为T(n)=1,其最高项                为0,因此T(n)=O(1)。

     示例2:当有若干个循环语句时,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的频度f(n)决定的。

             x=1; 
            for(i=1;i<=n;i++) 
                  for(j=1;j<=i;j++)
                     for(k=1;k<=j;k++)
                           x++;   
          解答:T(n)=O(n3)
          推导步骤:忽略for循环中的计算次数。当n为1时,1*1*1;当n为2时,2*2*2...依此类推,每次执行的次数都为n*n*n。在得到T(n)=n3,不存在加法常                  数,因此步骤1保持不变。取得最高阶仍为n3,因此最终的解为n3
  
        示例3:对数
        
 
          
         解答:T(n)=O(logn)
 

  算法最坏情况和平均情况

       针对算法的这两个情况,从字面上我们就可以知晓。所以无须赘述。而我们提到的运行时间指的都是最坏情况下的运      行结果。 

    

  算法的空间复杂度

     概念:法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作: S(o)= O(f(o)) ,其中, 0 为问题的规模, f(n)为语          句关于n 所存储空间的函数。

         一个算法在计算机存储器上所占用的存储空间,包括存储算法本身所占用的存储空间,算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储        空间这三个方面。

 

参考资料:http://www.nowamagic.net/librarys/veda/detail/2197 算法的空间复杂度

      http://www.nowamagic.net/librarys/veda/detail/2195 算法的时间复杂度

      http://blog.csdn.net/booirror/article/details/7707551  算法的时间复杂度举例

第二篇 算法概述及复杂度