Better

Ethan的博客,欢迎访问交流

复杂度简单理解

写代码时,考虑复杂度是很重要滴!前端代码也是需要注意代码性能的!

时间复杂度

对于代码的执行效率,我们通常不能通过在电脑上的执行时间进行比较,一个有效的替代方法是计算算法对一定规模的输入数据需要进行多少次操作来衡量算法的快慢。一般我们分析算法时,会假设输入数据规模是n, 然后计算算法需要的操作数f(n)。因为现在的计算机每秒都能做上亿次运算,而且我们处理的实际问题一般规模也比较大,所以我们一般只关心算法在输入规模比较大时的性能表现。并且我们一般只保留f(n)中占最大比重的项,忽略比重比较小的项和常数项。比如某一个算法对输入规模为n的数据所需要的操作数是f(n) = 0.5 n^3 + 20 n^2 + 3 n log(n) + 100. 我们就会用近似函数f(n) = n^3来表示这个算法的时间复杂度。

因此时间复杂度是总运算次数表达式中受n的变化影响最大的那一项(不含系数)

我们一般认为多项式时间复杂度内的算法,我们在实际生活中是可以接受的。像时间复杂度是指数增长的算法,我们一般认为是不实用的算法或时间复杂度过高的算法。

计算方法

一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。

一般情况下,算法的基本操作重复执行的次数是模块n的某一个函数f(n),因此,算法的时间复杂度记做:T(n)=O(f(n))。

在计算时间复杂度的时候,先找出算法的基本操作,然后根据相应的各语句确定它的执行次数,再找出T(n)的同数量级(它的同数量级有以下:1,Log2n ,n ,nLog2n ,n的平方,n的三次方,2的n次方,n!),找出后,f(n)=该数量级,若T(n)/f(n)求极限可得到一常数c,则时间复杂度T(n)=O(f(n))。

例:算法:

for(i=1;i<=n;++i){
     for(j=1;j<=n;++j){
         c[ i ][ j ]=0; //该步骤属于基本操作 执行次数:n^2
          for(k=1;k<=n;++k)
               c[ i ][ j ]+=a[ i ][ k ]*b[ k ][ j ]; //该步骤属于基本操作 执行次数:n^3
     }
  }

则有 T(n)= n^2+n^3,根据上面括号里的同数量级,我们可以确定 n^3为T(n)的同数量级,则有f(n)= n^3,然后根据T(n)/f(n)求极限可得到常数c,则该算法的 时间复杂度:T(n)=O(n^3)。

按数量级递增排列,常见的时间复杂度有:

常数阶O(1),对数阶O( log n ),线性阶O(n),线性对数阶O(nlog2n),平方阶O(n^2),立方阶O(n^3),...,k次方阶O(n^k),指数阶O(2^n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。

常用的时间复杂度所耗费的时间从小到大依次是:O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)

complex.png

常数阶

int n = 0; 
printf(“oneSong”);
printf(“oneSong”);
printf(“oneSong”);
printf(“oneSong”);

上面这段代码的时间复杂度是O(1)。因为,按照时间复杂度的定义来说,n和问题的规模没有关系。当然,按照时间复杂度计算方法第一条也可以得出结果为O(1)。

线性阶

int i , n = 10086, sum = 0;
for( i=0; i < n; i++ )
{
    sum = sum + i;
}

上面这段代码的时间复杂度是O(n),因为问题规模会随着n的增长而变得越来越大,并且这种增长是线性的。

平方阶

int i, j, n = 998;
for( i=0; i < n; i++ )
{
    for( j=0; j < n; j++ )
    {
        printf(“oneSong”);

}

上面这段代码外层执行n次,外层循环每执行一次,内层循环就执行n次,那总共程序想要从这两个循环出来,需要执行n*n次,也就是n的平方。所以这段代码的时间复杂度为O(n^2)。下面来看一些特别的例子:

//循环了(n+n-1+n-2+...+1)≈(n^2)/2,因为时间复杂度是不考虑系数的,所以也是O(n^2)
for(i=1;i<=n;i++)
     for(j=i;j<=n;j++)
         s++;
//循环了(1+2+3+...+n)≈(n^2)/2,当然也是O(n^2)
for(i=1;i<=n;i++)
     for(j=1;j<=i;j++)
          s++;
//循环了(1^2+2^2+3^2+...+n^2)=n(n+1)(2n+1)/6(这个公式要记住哦)≈(n^3)/3,不考虑系数,自然是O(n^3)
 for(i=1;i<=n;i++)
      for(j=1;j<=i;j++)
           for(k=1;k<=j;k++)
                x=x+1;

对数阶

int i = 1, n = 100;
while( i < n )
{
    i = i * 2;
}

由于每次i*2之后,就距离n更近一步,假设有x个2相乘后大于或等于n,则会退出循环。于是由2^x = n得到x = log(2)n,所以这个循环的时间复杂度为O(logn)。

另外,在时间复杂度中,log(2,n)(以2为底)与lg(n)(以10为底)是等价的,因为对数换底公式:

log(a,b)=log(c,b)/log(c,a)

所以,log(2,n)=log(2,10)*lg(n),忽略掉系数,二者当然是等价的。

空间复杂度

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

在程序开发中,我们所指的复杂度不做特别说明的情况下,就是指时间复杂度。现在的硬件发展速度之快使得我们完全可以不用考虑算法所占的内存,通常都是用空间 换取时间。加之算法的空间复杂度比较难算,所以,无论是在考试中还是在项目开发中,我们都侧重于时间复杂度。所以空间复杂度,略过。



留言