时间复杂度说白了就是运行一个算法需要消耗多少CPU指令周期,但是这个计算非常受到软硬件环境的影响,比如
A=A+B;
如果A是32位数,B也是32位数,所在的环境是32位机,这句话只需要1个指令周期即可完成。
但是如果A是64位数,B也是64位数,环境是32位机,这句话需要6个指令周期才能完成。
另一方面,对于稍微复杂一点的程序,精确计算CPU指令周期几乎是个不可能的,所以我们需要简化它。
因此,为了屏蔽环境影响,并且为了简化,我们只用渐进趋势来估计时间复杂度。所谓渐进趋势,就是n趋于无穷大的时候,算法复杂度趋于n的哪个函数。
下面是引入一些数学上的定义:
1. 如果存在正常数c和n,使得当N>n时,T(N)<=cf(N),则记为T(N)=O(f(N))
2. 如果存在正常数c和n,使得当N>n时,T(N)>=cg(N),则记为T(N)=Ω(g(N))
3. T(N)=θ(h(N)),当且仅当T(N)=O(h(N))且T(N)=Ω(h(N))
4. 如果T(N)=O(p(N))且T(N)!=θ(p(N)),则T(N)=o(P(N))
实际应用当中,在使用θ时,许多人更愿意用O;另外,当需要用到Ω时,还是使用O。。。所以我们平时经常见到O,而几乎没有见到别的符号。
有一个可用的方法来确定f(N)和g(N)的关系,即洛必塔法则:
若lim f(N)=无穷且 lim g(N)=无穷,则lim f(N)/g(N)=lim f(N)的导数/g(N)的导数
若结果0,f(N)=o(g(N));
若极限是c!=0,f(N)=θ(g(N))
极限是∞,g(N)=o(f(N))
极限摆动,二者无关
递归的算法如何计算算法复杂度?
比如Fibonacci数列,它的递归算法如下:
long int Fib(int N){
if(N<=1)
return 1;
else
return Fib(N-1)+Fib(N-2);
}
计算输入为N的运行时间值是T(N)=T(N-1)+T(N-2)+2; 后面的那个2是Fib(0)和Fib(1)的时间。
Example 1:
int a=0;
for(int i=0;i<N;i++){
for(int j=0;j<i;j++) {
a++;
}
这个例子里面,算法复杂度是O(1+2+3...N-2), 按照等差数列求和公式,应该是 O((N-1)*(N-2)),所以算法复杂度是O(N的平方)
Example 2:
int Count(BYTE v){
int num=0;
while(v){
num+=v&0x01;
v>>=1;
}
return num;
}
这种算法的复杂度是多少呢?
它计算了log(v)次,也就是v的二进制位数。
解法2:
int Count(BYTE v){
int num=0;
while(v){
v&=(v-1);
num++;
}
return num;
}
算法复杂度为v中1的个数
解法3:
A=A+B;
如果A是32位数,B也是32位数,所在的环境是32位机,这句话只需要1个指令周期即可完成。
但是如果A是64位数,B也是64位数,环境是32位机,这句话需要6个指令周期才能完成。
另一方面,对于稍微复杂一点的程序,精确计算CPU指令周期几乎是个不可能的,所以我们需要简化它。
因此,为了屏蔽环境影响,并且为了简化,我们只用渐进趋势来估计时间复杂度。所谓渐进趋势,就是n趋于无穷大的时候,算法复杂度趋于n的哪个函数。
下面是引入一些数学上的定义:
1. 如果存在正常数c和n,使得当N>n时,T(N)<=cf(N),则记为T(N)=O(f(N))
2. 如果存在正常数c和n,使得当N>n时,T(N)>=cg(N),则记为T(N)=Ω(g(N))
3. T(N)=θ(h(N)),当且仅当T(N)=O(h(N))且T(N)=Ω(h(N))
4. 如果T(N)=O(p(N))且T(N)!=θ(p(N)),则T(N)=o(P(N))
实际应用当中,在使用θ时,许多人更愿意用O;另外,当需要用到Ω时,还是使用O。。。所以我们平时经常见到O,而几乎没有见到别的符号。
有一个可用的方法来确定f(N)和g(N)的关系,即洛必塔法则:
若lim f(N)=无穷且 lim g(N)=无穷,则lim f(N)/g(N)=lim f(N)的导数/g(N)的导数
若结果0,f(N)=o(g(N));
若极限是c!=0,f(N)=θ(g(N))
极限是∞,g(N)=o(f(N))
极限摆动,二者无关
递归的算法如何计算算法复杂度?
比如Fibonacci数列,它的递归算法如下:
long int Fib(int N){
if(N<=1)
return 1;
else
return Fib(N-1)+Fib(N-2);
}
计算输入为N的运行时间值是T(N)=T(N-1)+T(N-2)+2; 后面的那个2是Fib(0)和Fib(1)的时间。
那么怎么求Fib(N)的时间复杂度呢?我们可以用数学归纳法证明Fib(N)<(5/3)的N次方,同样我们也可以用同样的方法证明Fib(N)>=(3/2)的N次方。因此递归的函数很难直观的计算复杂度。
Example 1:
int a=0;
for(int i=0;i<N;i++){
for(int j=0;j<i;j++) {
a++;
}
这个例子里面,算法复杂度是O(1+2+3...N-2), 按照等差数列求和公式,应该是 O((N-1)*(N-2)),所以算法复杂度是O(N的平方)
Example 2:
对于一个字节(8bit)的无符号整形变量,求其二进制表示中 “1” 的个数,要求算法的执行效率尽可能高。
解法1:int Count(BYTE v){
int num=0;
while(v){
num+=v&0x01;
v>>=1;
}
return num;
}
这种算法的复杂度是多少呢?
它计算了log(v)次,也就是v的二进制位数。
解法2:
int Count(BYTE v){
int num=0;
while(v){
v&=(v-1);
num++;
}
return num;
}
算法复杂度为v中1的个数
解法3:
写一个Hash表,用空间换时间,这样算法复杂度为O(1)
一些常见的算法复杂度:
O(1)
基本运算, +,-,*,/,%,寻址
Hash的期望复杂度(不碰撞的情况下)
O(logn)
二分查找
O(n1/2)
枚举约数
O(n)
线性查找
建立堆
O(nlogn)
归并排序
快速排序的期望复杂度
基于比较排序的算法下界
O(n2)
集合里枚举所有二元组,朴素最近点对
O(n3)
集合里枚举三元组
Floyd最短路
普通矩阵乘法
O(2的n次方)
枚举全部的子集
O(2的n次方*n)
TSP的动态规划算法
O(n!)
枚举全排列
O(n的n次方)
枚举[1..n]的n维数组的全部元素……