时间复杂度和空间复杂度

衡量算法效率的两种方法

  • 事后分析法
  • 事前分析法(估算)

1、事后分析法

使用计算机内部的计时器功能(节拍),但是有些算法可能运行下来,也不足一个节拍,故可以采用多次运行的策略,达到预期的效果。

  缺点

  • 必须先多次运行依据算法编制的程序
  • 时间统计量依赖于计算机的软硬件环境

2、事前分析法

与算法执行时间相关的影响因素

  • 算法策略
  • 问题规模
  • 所使用的语言
  • 编译器产生代码的质量
  • 机器运行的速度

抛弃语言、编译器、运行机器的差异,我们知道前两者是我们分析算法效率的有效途径。

算法时间的度量

运行时间 = ∑算法中每条执行语句的执行时间

运行时间 = ∑(频度 ∗ 语句执行一次所需的时间)

若设每条语句执行的时间一样

运行时间 = ∑(每条语句重复执行的次数)

举个例子,下面有三个算法。

1、执行了n+1次

int i, sum = 0, n = 100; // 执行1次
for( i=1; i <= n; i++ ) 
{
sum = sum + i; // 执行n次
}

2、执行了2次

int sum = 0, n = 100; // 执行1次
sum = (1+n)*n/2; // 执行1次

3、执行了2n2+1次

int i, j, x=0, sum=0, n=100; // 执行1次
for( i=1; i <= n; i++) 
{
for( j=1; j <= n; j++ ) 
{
x++; // 执行n2次方次
sum = sum + x; // 执行n2次方次
}
}

然而,我们只需要选出对所研究的问题来说是基本操作的语句,以其在算法中重复执行的次数来作为衡量算法运行时间的衡量准则。算法执行时间与基本操作的执行次数之和成正比!

则算法1,可以看成执行了n次,算法2为1次,算法3为n2次。如下图

程序运行的好坏依赖于算法的策略和输入的规模。换句话说,衡量算法效率的指标就是随着问题规模的增大,算法的运行时间会处于哪个量级。

那么如何来比较算法之间的量级呢?

通过几个问题来分析一下

问题一:假设两个算法的输入规模都是n,算法A要做2n+3次操作,B要做3n+1次操作,试比较算法A和算法B的效率。

当n = 1时,算法A1是不如算法B1的,当n = 2时,两者效率是相同的,当n > 2时,A1算法就优于B2算法了。通过分析也知道,自此,A1与B1的差距变大了,总体上A1是优于B1的。

概念

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

从问题一的分析可以发现,随着n的增大,常数项+3和+1并不会影响最终的增长变化。于是我们得出结论1,即可以忽略加法常数。

 问题二:假设两个算法的输入规模都是n,算法C要做4n+8次操作,D要做2n2+1次操作,试比较算法C和算法D的效率。

我们发现由于指数的差距,就算去掉与最高项相乘的常数和其他次要项,两者的结果还是没有改变,随着n的增大,E2还是远远小于D2。所以有结论2,与最高项相乘的常数并不重要,可以忽略。

问题三:假设两个算法的输入规模都是n,算法E是2n2+3n+1,算法F是2n3+3n+1 ,试比较算法E和算法F的效率。

可以很明显的看到指数较大的函数增长很快。所以结论三是最高项的指数非常重要,指数越大,增长速度越快。

问题四:假设三个算法的输入规模都是n,算法G是2n2,算法H是3n+1,算法I是 2n2+3n+1。,试比较算法G、H和算法I的效率。

 当n很大时,H已经无法和G比较了,更别说 I 算法了,所以结论四是判断一个算法的效率时,函数的常数项和其他次要项可以忽略,应该关注最高次的项。

算法时间复杂度

  • 在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。
  • (渐进)时间复杂度 T(n) = O( f(n) ), n-问题规模

表示随问题规模n的增大,算法执行时间的增长率/阶和f(n)的增长率/阶相同。

  • 其中f(n)是问题规模n的某个函数。
  • 大O符号(英语:Big O notation)是用于描述函数渐近行为的数学符号,也可理解为Order(阶)的缩写。

那如何来分析算法的(渐近)时间复杂度,即推导大O阶呢?三个规则如下:

1、用1取代运行时间内的所有加法常数。

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

3、如果最高项存在且不是1,除去与最高项相乘的常系数。

举几个例子

常数阶

int a = 100;  //执行一次
int b = 200;  //执行一次
int sum = a + b;  //执行一次
printf("%d
", sum);  //执行一次

每行执行一次,共4次,f(n) = 4,即T(n) = O(4),算法的时间复杂度为O(1)。(规则1)

线性阶

void func()
{
    int i, sum = 0;  //执行一次
    for (i = 0; i <= 100; i++)
    {
    sum += i;  //执行n次
    }
    printf("%d
", sum);  //执行一次
}

程序段的执行次数为1+n+1,f(n) = n + 2,T(n) = O(n),算法的时间复杂度为O(n)(规则2)

平方阶

void func()
{
    int i, j, n = 100;
    for( i=0; i < n; i++ )
    {
        for( j=0; j < n; j++ )
        {
            printf(“I love China.
”);
        }
    }
}

程序段的执行次数为n*n+1,f(n) = n*n,T(n) = O(n2),算法的时间复杂度为O(n2)(规则2)

void func()
{
    int i, j, n = 100;
    for( i=0; i < n; i++ )
    {
        for( j=i; j < n; j++ )
        {
            printf(“I love China.
”);
        }
    }
}        

程序段的执行次数为1+n(n+1)/2 = n2/2+n/2,f(n) = n*n,T(n) = O(n2) ,算法的时间复杂度为O(n2)。(规则2、3)

对数阶

void func()
{
    int i = 1;
    do
    {
        i *= 2; 
    }
    while (i < n);
} 

按照前面的知识,可知程序循环了f(n)次,2f(n) = n,f(n)  = log2n,T(n) = O(log2n) ---> T(n) = O(log n)(规则2、3,对数的底数为2并不是分析问题的关键,关键是对数级别)

更多

 

算法空间复杂度

  • 空间复杂度: 因算法引入的额外辅助空间
  • (渐进)空间复杂度: S(n) = O(f(n))
    • 若所需额外空间相对于输入数据量来说是常数,则称此算法为原地工作
    • 若所需存储量依赖于特定的输入,则通常按最坏情况考虑

常用的空间复杂度量级有O(1),O(n),O(n2).

当我们定义了一个普通的整形变量时,例如(int i = 1;),此时的空间复杂度就为O(1),当定义一个一维数组时,空间复杂度就为O(n),那最后一个复杂度可想而知了,会对应什么。

纸上得来终觉浅,绝知此事要躬行
原文地址:https://www.cnblogs.com/modesty-boy/p/13363195.html