第二章 算法分析
1. 算法复杂度表示(大O表示法)
- 概念:
大O表示法:称一个函数g(n)是O(f(n)),当且仅当存在常数c>0和n0>=1,对一切n>n0均有|g(n)|<=c|f(n)|成立,也称函数g(n)以f(n)为界或者称g(n)囿于f(n)。记作g(n)=O(f(n))。 定义:如果一个问题的规模是n,解这一问题的某一算法所需要的时间为T(n),它是n的某一函数。T(n)称为这一算法的“时间复杂度”。当输入量n逐渐加大时,时间复杂度的极限情形称为算法的“渐近时间复杂度”。
即运行时间指一种算法的运算时间的增速,并不是以秒为单位的速度。一个算法,并不仅仅要知道他运行的时间,还要知道其随着数据内容的增加他的运算时间是如何增加的。 O(n) 中n是操作的次数。
表示意义
- 他表示一种算法在最糟糕的情况下需要计算的次数。比如在一个电话簿(n个电话)中找一个电话,利用快速查找,最好的情况是第一次就找到,最坏的情况是第n次找到。这个运行时间要按照最糟糕的情况算 即O(n)
-我们常用大O表示法表示时间复杂度,注意它是某一个算法的时间复杂度。大O表示只是说有上界,由定义如果f(n)=O(n),那显然成立f(n)=O(n^2),它给你一个上界,但并不是上确界,但人们在表示的时候一般都习惯表示前者。此外,一个问题本身也有它的复杂度,如果某个算法的复杂度到达了这个问题复杂度的下界,那就称这样的算法是最佳算法。
- 他表示一种算法在最糟糕的情况下需要计算的次数。比如在一个电话簿(n个电话)中找一个电话,利用快速查找,最好的情况是第一次就找到,最坏的情况是第n次找到。这个运行时间要按照最糟糕的情况算 即O(n)
常见的大O运行时间(从快到慢进行排序)
- O(㏒n) 对数时间 比如二分查找
- O(n) 线性时间 比如简单查找
- O(n*㏒n) 比如快速排序
- O(n²) 比如选择排序
- O(n!) 比如旅行者问题
复杂度与时间效率的关系:
c < log2n < n < n*log2n < n2 < n3 < 2n < 3n < n! (c是一个常量)
2. 运行时间计算
例:计算∑ i³
public static int sum(int n){
int sum;
⑴ sum = 0;
⑵ for(int i = 1; i <= n; i++){
⑶ sum += i * i * i;
}
⑷ return sum;
}
分析:所有的声明均不计时间。第一行赋值语句和第四行返回值语句各占一个时间单位。第三行运行一次占4各时间单位(一次赋值,两次乘法,一次加法),总共运行N次占4N个时间单位。第二行占(一次赋值,N+1次比较,N次自加)共2N+2个时间单位。忽略调用方法和返回值的开销,得到总量是6N+4个时间单位。因此我们说该方法是O(N)的。
由于我们有了大O的结果,因此就存在许多可以采取捷径而不影响最后运行结果的方法。例如在第三行中我们无需知道运行一次到底占几个时间单位,我们只要知道它是O(1)的就行,至于赋值语句等与for循环比起可以忽略最后得到的结果还是O(N)。所以为了简化分析,我们不必计较具体开销,这使得我们得到一般法则。
法则一——for循环:
一个for循环运行时间至多是该for循环内部那些语句(包括测试)的运行时间乘以迭代次数。一般情况下由于大O内部任何简化都是可能的,所以一般简单的for循环得到的结果是O(N)的。
法则二——嵌套的for循环:
从里向外分析循环。在一组嵌套循环内部的一条语句运行时间为该语句的运行时间乘以该组所有的for循环大小的乘积。例如双重for循环内部有一条赋值语句,则结果为O(N²)。
法则三——顺序语句:
将各个语句的运行时间求和即可。(O(f(N))+O(g(N))= max(O(f(N)),O(g(N))
法则四——if / else语句:
对于程序片段
if(条件)
语句
else
语句
一个if / else语句的运行时间不会超过判断的运行时间加上两个语句中运行时间较长的那个语句的运行时间。
2.1 一个关于递归的例子
public static long fib(int n) {
1 if (n <= 1) {
2 return 1;
} else {
3 return fib(n - 1) + fib(n - 2);
}
}
fib(n)运行时间公式:
式中2指的是第1行的工作和第3行的加法
可证明 当N>4; fib(N) >= (3 / 2) ^ N