《大话数据结构》--时间复杂度

1,059 阅读3分钟

算法时间复杂度的定义

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

问题规模n和执行次数T(n)

一般随着n增大,T(n)增长最慢的算法被称之为最优算法。

一个程序的运行时间,依赖于算法的好坏和问题的输入规模。所谓问题输入规模是指输入量的多少。

计算1+2+3+······+100的结果

两个算法的第一行和最后一行语句一样,所以我们关注中间部分。我们把循环看做整体,忽略头尾循环判断的开销,那么这两个算法其实就是n次和1次的差别。

在上述算法中,

第一种n=100,T(n) = 100;

第二种n=100,T(n) = 1;

运行时间和有消耗的基本操作的执行次数成正比

大O记法

用大写O()来体现算法时间复杂度的记法,称之大O记法。O(1)叫做常数阶、O(n)叫做线性阶、O(n2)称作平方阶。

推导大O阶方法

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

常数阶

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

运行函数f(n) = 3。根据推导大O阶方法,

  1. 把常数项的3改为1
  2. 没有最高阶项,所以算法复杂度为O(1).

线性阶

for (int i = 0; i < n; i++) {     
    // 时间复杂度为O(1)的程序步骤
}

分析算法的复杂度,关键是分析循环体结构的运行情况。由于循环体中代码需要执行n次,所以时间复杂度为O(n)

对数阶

int count = 1;
while(count < n) {    
    count = count * 2; // 时间复杂度为O(1)的程序步骤
}

有多少个2相乘后就会大于n,则会退出循环。有2X = n得到n = log2n。所以这个循环的复杂度为O(logn)

平方阶

for (int i = 0; i < n; i++) {    
    for (int j = 0; j < n; j++) {        
        // 时间复杂度为O(1)的程序步骤    
    }
}

内循环的时间复杂度为O(n),再循环n次,时间复杂度为O(n2)。如果外循环的次数改为m,时间复杂度为O(m*n)。

for (int i = 0; i < n; i++) {    
    for (int j = i; j < n; j++) {        
        // 时间复杂度为O(1)的程序步骤    
    }
}

当i=0时,内循环循环n次,i=1时,循环n-1次·······,当i=n-1时,执行了1次。 执行次数 = n + (n-1) + (n-2) + ····· + 1 = n(n-1)/2 = n2/2 + n/2.

  1. 没有加法常数,不予考虑。
  2. 只保留最高项。因此为 n2/2
  3. 去除这个项相乘的常数,也就是1/2。

最终复杂度为O(n2)

static void function(int count) {    
    for (int i = 0; i < n; i++) {        
        // 时间复杂度为O(1)的程序步骤    
    }
}

function(n);    // 执行次数 n次
for (int i = 0; i < n; i++) {    // 执行次数 n*n次
    function(n);
}    
for (int i = 0; i < n; i++) {     // 执行次数 n*(n+1)/2次
    for (int j = i; j < n; j++) {        
        // 时间复杂度为O(1)的程序步骤    
    }
}

执行次数f(n) = n + nn + n(n+1)/2 = 3n2/2 + 3n/2,根据推导大O阶方法,最终复杂度也为O(n2)

常见复杂度

O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn )

上述介绍了常见的常数阶,对数阶,线性阶,平方阶等。而像O(n3),过大的n都会使结果不现实,而指数阶O(2n)和阶乘阶O(n!)等除非n很小,否则哪怕n=100,都是噩梦般的运行时间,一般不做讨论。

最坏情况和平均情况

一般我们查找1-n中的某个数字,最好结果是放在第一个位置,时间复杂度为O(1)。最坏就是在最后的位置,时间复杂度为O(n)。

最坏情况运行时间是一种保证,就是运行时间将不会再坏了。通常提到的运行时间都是最坏情况的运行时间。

平均时间是所有情况中最有意义的,因为他是期望的运行时间,上述问题平均查找时间是n/2。

对算法的分析,一种是计算所有情况的平均值,称为平均时间复杂度。一种是计算最坏情况下的时间复杂度,称为最坏时间复杂度。一般没有特殊说明,都是指最坏时间复杂度。