度量一个程序的执行时间一般有两种方法:
1.事后统计法:计算机内部有计时功能甚至可以精确到毫秒级别。
但这种方法有两个明显的缺陷:
(1)必须先运行程序
(2)依赖计算机硬件,软件等环境因素,容易遮盖算法本身的优劣
(ps:测试结果受数据规模的影响很大,比如:对同一个排序算法,待排序数据的有序度不一样,排序的执行时间会有很大的差别,极端情况下,若数据已经是有序的,那么排序算法不需要做任何操作,执行时间会非常短,其次,若数据规模太小,测试结果也无法真实地反映算法的性能,比如,对于小规模数据,插入排序可能比快速排序要快。)
2.事前分析估算法:取决于以下因素——>
(1)一句的算法用何种策略
(2)问题的规模
(3)书写程序的语言
(4)编译程序所产生的机器代码质量
(5)机器执行指令的速度
以上都是课本上的内容,接下来大概讲讲如何对算法进行粗略的计算:
int main(){
int i=0;
int sum=0;
for(i=0;i<n;i++){
cin>>j;
sum+=j;
}
}
第二三行代码分别需要一个unit_time的执行时间,第四五六行都运行了n遍,素以需要3n*unit_time的执行时间,所以这段代码的总执行时间是(3n+2)*unit_time。
int main(){
int sum=0;
int i=1;
int j=1;
for(:i<=n;i++){
j=1;
for(:j<=n;j++){
sum=sum+i*j;
}
}
}
第二三四行代码,每行只需要一个unit_time 即可,而第五六行代码循环执行了n遍,需要2n*unit_time的执行时间,第七八行代码循环执行了n^2遍,所以需要2n^2*unit_time的执行时间。整段代码的总执行时间是T(n)=(2n^2+2n+3)*uxit_time。
由此可得每行代码的执行时间与执行次数成正比。
当n很大时,可以当成100000,10000000,而书上的公式中的低阶,常量,系数三部分并不左右增长趋势,所以可以忽略,我们只需要关注最大量级就可以了,因此,上两例中的时间复杂度为:T(n)=O(n), T(n)=O(n^2)
以下有三个比较实用的方法:
- 只关注循环执行次数最多的一段代码
第一个例子中,第二三行代码都时常量级的执行时间,与n无关,所以对时间复杂度无影响,可忽略。循环执行次数最多的是第四五六行代码,所以重点分析这块代码,被执行了n次,因此总的时间复杂度为O(n)。
2.加法法则:总复杂度等于量级最大的那段代码的复杂度
int cal(int n){
int sum_1=0;
int p=1;
for(;p<100;++p){
sum_1=sum_1+p;}
}
int sum_2=0;
int q=1;
for(;q<n;++q){
sum_2=sum_2+q;}
int sum_3=0;
int i=1;
int j=1;
for(;i<n;++i){
j=1;
for(;j<=n;++j):
sum_3=sum_3+i*j;
}
}
这个代码分三部分,分别求sum1,sum2,sum3.我们分别分析每一部分的时间复杂度,再取一个量级最大的作为整段代码的复杂度。
Sum1这段代码被执行了100次,所以是一个常量级,与n的规模无关
(ps:即便这段代码被执行了10000000次,只要次数已知,跟n无关,照样是常量级的执行时间,可以忽略)
第二三段代码的时间复杂度是O(n)和O(n^2)。
综合这三段代码的时间复杂度,取其中最大量级n^2,所以整段代码的时间复杂度为O(n^2).
也就是说,总的时间复杂度就等于量级最大的那段代码的时间复杂度。
3.乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
int cal(int n){
int ret=0;
int i=0;
for(;i<n;++i{
ret+=f(i);}
}
int f(int n){
int sum=0;
int i=1;
for(;i<n;++i){
sum+i;}
return sum;}
集中常见时间复杂度:
O(1), O(logn),O(n),O(nlogn),O(n^2),O(n^3)
- O(1)
int i=4;
int j=3;
int k=i+j;
O(1)是常量级时间复杂度的一种表示方法。而不是说只执行了一行代码,所以这段代码,计时有三行,但时间复杂度为O(1),而不是O(3)。
一般情况下,只要算法中不含循环,递归语句,即使有成千上万行代码,它的时间复杂度都是O(1)。
2.O(logn),O(nlogn)
对数阶时间复杂度非常常见,如:
i=1;
while(i<=n){
i=i*2;}
i的取值为2,4,8......,(2^x)<=n;x等于log2(n) [2为底],所以这段代码的时间复杂度为O(log2(n));
再看下列代码
i=1;
while(i<=n){
i=i*3;}
i的取值为1,3,9,27,81.....(3^x)<=n,这段代码的时间复杂度为O(log3(n)),
实际上,不管是2,3还是别的数为底,我们可以把对数阶的时间复杂度都记为O(logn),
因为对数之间是可以相互转换的,log3(n)+log3(2)*log2(n),所以O(log3(n)=O(C*log2(n)),其中log3(2)是常量,可忽略,所以对数阶复杂度可表示为O(logn).
O(nlogn)就很容易啦,就是将上述代码嵌在一个执行n次的循环中就好了。
3.O(m+n),O(m*n)
int cal(int m,int n){
int sum_1=0;
int i=1;
for(;i<m;++i){
sum_1=sum1+i;}
int sum_2=0;
int j=1;
for(;j<n;++j){
sum_2+=j;}
return sum_1+sum_2;}
由于m,n两个数据规模未知,因此不能简单运用加法法则省略掉其中一个,所以代码的时间复杂度为O(m+n)