数据结构第二章——算法

来源:互联网 发布:sql中两个select语句 编辑:程序博客网 时间:2024/06/11 22:32

       算法就是解决问题的方法,前面的思想框架已经建立好了,那么接下来就应该到了实际的计算以及解决问题的步骤,算法简单来说就是每一步细节。

       数据结构中,算法可以算是中心思想,数据结构和算法是密不可分的,相辅相成。在数据结构中,就是用算法来解决每一个实际问题的。数据结构是思想,算法是操作。在计算机行业中,每一段代码就是一种算法,例如计算加减乘除的算法……。

       在计算问题的时候,虽然计算机没有思维能力,但是计算能力还是比常人要快的多,所以我们一般给计算机写好一段代码,就相当于告诉他,以后的所有数字放进来的时候,都是这样计算的,只要输入一串或者一个数字,就开始进行计算。这样,就相当于给了计算机思想,他就可以替代人来计算一些比较复杂繁冗的问题,这样也节省了很多时间。

       算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且,每条指令表示一个或多个操作。

      对于一个问题,可以有多种求解方法。而我们自然希望可以有一种很方便快捷的通解。在实际问题中,每一个问题所针对的算法都是不一样的。为了解决某个或某类问题,需要把指令表示成一定的操作序列,操作序列包括一组操作,每一个操作都完成特定的功能,这就是算法。

       算法的特性:输入、输出、有穷性、确定性、可行性。

       算法的输入输出是基本的要求,我们在编写代码的时候,希望此代码可以解决一类问题,所以计算机要输入别的数字,而结果自然是要可以在显示屏上边显示输出的。

       确定性:要求算法的每一个步骤都具有确定的含义,不会出现二义性。就是在计算的时候,计算机只有一种解决问题的途径,就像在继承的时候,当一个基类派生出两个不同的子类时,计算机只需要一种继承操作,而不需要有任何的争议去讨论基类的先后性。

       可行性:算法的,每一步都是可行的,也就是说,每一步都能够通过执行有限次数完成。可行性是比较好理解的,如果你写了一个代码,不能解决问题,或者不能计算出结果,那么这个代码肯定是没有实际意义的。

       一个好的算法应该满足以下几个条件:

                       1.正确性

                       2.可读性,我们所写的代码,一方面自己和计算机可以理解,另一方面,别人在阅读的时候也是可以很好的理解的。

                       3.健壮性,一个好的算法,一定要进行报错处理,我们在写代码的时候,要考虑到如果输入不合法的,比如int中的溢出,或者要求整数,输入了小数……,这些都是代码中的重要考虑问题。如果没有报错处理,一个再完美的代码,因为一个不合法的输入崩盘,实在太可惜,所以在编写代码的时候,一定要考虑到代码的报错系统。

                       4.时间效率高和存储量低,这个可能是所有程序猿的梦想。如果你写了一个小游戏,你肯定希望这个游戏在玩的时候反映特别快,而且战占得内存还小,这样游戏肯定特别火。或者在编写计算加和代码的时候,数字越来越大,那么所需的计算时间当然也会变得很长,我们当然希望代码可以高效率低内存的解决实际问题。

         既然我们希望计算机可以快速的解决问题,那么我们怎么知道、怎么计算一个代码在解决问题的时候所需要的时间呢?我们可以在计算机计算的时候开始计时,判断一个代码需要多长时间,这个算法也是可行的,但不是最好的。  在计算算法的效率的时候,有以下方法:

                1.事后统计方法:通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序运行时间进行比较,从而确定算法效率的高低。这种方法是不可取的,一个小代码的时间往往在一秒之内,这样他的时间差异就非常小。而且计算机和计算机本身处理器也是有差异的,所以一个代码在不同的计算机上边所运行的时间也有可能不一样。

               2.事前分析估计方法:在计算机编程之前,依据统计方法对算法进行估算。

         发现这个计算机和数学简直就是一家人呀,我的天,什么都要用到数学,一个实际问题,你得先分析成数学模型,在计算的时候还是要用到数学逻辑思维,而代码只是一个辅助的东西,他只是把问题和计算机联系起来。使得计算机可以模拟实际问题,原来自己的专业课是被数学牵制的,那理学院的学生岂不是个个都可以当编程大神,那为什么理学院和计算机院要分开,很明显没必要呀。微笑

          偶尔感慨一下有助于学习和记忆!敲打

          正题:一个问题,多种解决方法,而我们想找到一个耗时短的好代码,而怎么判断他的时间呢?事前分析估算就是计算输入量的多少和次数。

        例如百数求和:

             第一种算法:利用循环,从1到100,一次累加,最后得出结果。代码和执行次序如下

int i;sum=0;n=100;         //执行1次for(i=0;i<=n,i++)             //执行了n+1次sum+=i;                           //执行n次printf("%d",sum)              //执行1次

第二种算法:利用求和公式计算,代码和执行次序如下

int sum=0,n=100;      //执行一次sum=(1+n)*n/2;        //执行一次printf("%d",sum)        //执行一次
      两种算法中,第一个需要执行1+(n+1)+n+1=2n+3 次,第二种需要执行1+1+1=3次,这样比较,很明显第二个的时间要短。

       因为第一个代码有循环,而循环一次就是n,如果是多层循环就是n的多次方,所以一般的循环都要比普通代码时间长。

       而计算的时候,有时候不一定有n多时间就多,当n比较小的时候,有时候可能n
多的代码时间反而还比较短。所以引出一个定义,

        函数的渐进增长:给定两个函数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的某个函数。

      当随着时间的增长,T(n)增长最慢的算法是最好的算法。

      那么时间复杂度O要如何计算呢?例如刚才的两个代码,第一个执行了(2n+3 次,第二个执行了3次。在计算时间复杂度的时候,要将所有的常数加法都用1替换,将所有非次方的n的加法都用n替换;有最高阶保留最高阶项,这样第一个代码的O为n,第二个代码的O为1。总结计算方法:

      

     还有几个比较难计算的时间复杂度:

 线性阶:一般线性是循环的,所有我们只需要判断循环体中的次数就可以了,循环是n,时间复杂度就是O(n),循环是m,时间复杂度就是O(m)。

 对数阶:对数就要用到刚刚的除法,举例代码

int count=1;while(count<n){    cout=cout*2;     //短代码,时间复杂度为1;}
此题的计算思想是,当count在不断和2相乘的时候,结果大于n就跳出循环,所以可得到一个关系:得到。所以这个循环的时间复杂度为O(logn)。

平方阶:常用于循环体,内层循环和外层循环是相乘的关系,例如代码

int i,j;for(i=0;i<n;i++)    {       for(j=0;j<n;j++)
         {
              //时间复杂度为1的代码;
          }    }
    外层循环是n,内层循环也是n,所以这段代码的时间复杂度为O(n^2)。

    如果外循环变成了m,则时间复杂度为O(n*m)。

    再思考一下,如果循环是相互嵌套的呢?

    例如

   int ii,j;
   for(i=0;i<n;i++)
   {
       for(j=i;j<n;j++)
         {
             //时间复杂度为1的代码;
          }
    }
     在代码中,j是从i开始的, 当i=0的时候,内层循环n次,当i=1时,内层循环n-1次……,当i=n-1时,内层循环1次。所以总的执行次数为

               

所以最后的结果就是O(n^2);

在计算的时候,真的得数学特别好;哭,要不然根本就没办法计算时间复杂度的。内外层循环的时候,还要计算数列,计算指数……,所以要想学好编程,数学得是大神呀!

     在实际问题中,免不了要调用自定义的函数,那调用的函数的时间复杂度怎么计算呢?其实计算调用函数,就是和之前计算的方法一样,计算出来之后,然后观察调用他的那个函数是循环还是普通常数阶。是循环就乘,不是循环就加,或者再判断有没有数学关系之类的,总之就是和之前的方法一样,不过就是多了几步,麻烦了一点而已。

   要记住在对数阶中,是不需要关心下标的,无论是还是其他的数字,时间复杂度都是O(logn)。用图来表示常用时间复杂度所耗费的时间的大小


算法的时间复杂度计算公式:S(n)=O(f(n)),n为问题的规模(多少语句和循环的次数),f(n)为语句n所占存储空间的函数。在计算了时间复杂度的前提下,编写代码的时候就可以很轻易的找到最便捷的方法,最优化的方法。这样一个代码就是最好的代码。而时间复杂度的计算显然是一件不容易的事情,但是只要心细,认真,时间复杂度不是难计算,只是比较麻烦,在计算的时候,要删除别的吗、次要数据,这样很容易混淆。一个合格的程序猿,应该铭记报错和时间复杂度的计算,虽然在CPU高速发展的时代里,计算时间复杂度仿佛不是什么重要的问题,但是如果将这个作为借口,也是不合理的,我们应该将知识学的特别稳,而不是投机取巧、偷工减料。编写代码的时候,用时间复杂度可以找到更完美的代码,我们为什么不呢?还有一点就是程序猿在编写代码的时候,为了节省时间,可以利用空间换取时间的概念,将空间放大,时间缩短。但这并不是大体方法,也只是有针对性的解题方法。基本上,这些就是数据结构的名词和概念。从以后开始,就要正式攀数据结构这座大山了,心里很是很胆怯的微笑。本着程序猿的精神,继续探究。