声明:在本文章当中的基本执行次数计算时,求和边界并没有做出准确区别,仅仅是估计。
知识点:
时间复杂度的意义:
同样的问题,就拿寻找最短路来说有各种各样的算法可以做到,他们在时间与空间上的复杂度都不尽相同,但是在一定数据规模下有的算法效率高,有的算法效率低。如果我们熟悉算法的各种复杂度,就可以根据数据的规模选择最为合适的算法,甚至是在原有算法的基础上进行修改。因此时间复杂度可以认为是衡量算法在一定数据规模下执行效率的标准。
衡量代码好坏的标准:
1.运行时间。同样的一个问题,A代码够用10s计算出结果,而B代码需要花费1000s甚至是更多。那么显然A代码执行的效率更高。
2.占用空间。A代码解决问题需要占用5M的空间,而B代码需要占用500M的空间,那么显然是A代码的占用空间小。
注:
1.在占用空间允许的情况下,我们可以更换算法,用更大的空间换取更小的运行时间;反之,我们也可以在时间允许的情况下,用时间换取空间。
2.在分析占用空间,也就是空间复杂度的时候,我们往往是计算除必须占用的空间之外的所需空间,也就是额外的空间。
3.算法分析的两个主要方向是时间复杂度和空间复杂度。
基本操作执行次数:
顾名思义就是代码执行了多少次。
如果我们用T(n)表示执行次数,那么我们来看一下如下的例子。
例1:
你完成一门科目的作业用掉6天,那么现在有n个科目的作业需要你来完成那么要几天?我们可以记做T(n)= 6n;
例2:
你现在有1000道数学题要完成,并且题目是由简到难,因此第一天你能做500道,第二天做250,第三天做125.,问到底几天就剩下一道题目没做?
这个问题也就是1000不断地除以2问几次之后能变成1,因此我们用对数函数可以得到,T(n)= log(1000),那么如果是n道题目,你需要T(n) = log(n);
例3:
因为你英语不好,所以你特别讨厌英语,你的英语作业都是抄答案,无论有多少题目你总是能够一天抄完,那么显然T(n)=1;
(仅仅是举个例子,英语其实还是很重要的!!!)
例4:
你的假期是n天,在这n天里面你励志要好好做题来提高自己,你第一天做1道题目,第二天做2道,以此类推;
T(n) = 0.5n^2+0.5n;
小结:
例1.T(n) = 6n,执行次数是线性的;
例2.T(n)= log(n),执行次数是对数的;
例3.T(n)= 1,执行次数是常亮的;
例4.T(n)= T(n) = 0.5n^2+0.5n,执行次数是一个多项式;
渐进时间复杂度:
定义:
若存在函数 f(n),使得当n趋近于无穷大时,T(n)/ f(n)的极限值为不等于零的常数,则称 f(n)是T(n)的同数量级函数。
记作 T(n)= O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
渐进时间复杂度用大写O来表示,所以也被称为大O表示法。
推导渐进时间复杂度的原则:
1.如果运行时间是常量数量级,用常数1表示;
2.只保留时间函数中的最高阶项;
3.如果最高阶项存在,则省去最高阶前面的系数;
因此:
例1.T(n)= O(n);
例2.T(n)= O(logn);
例3.T(n) = O(1);
例4.T(n)= O(n^2);
不同时间复杂度的差异:
用O(n)与O(n^2)来举例,在n非常小的时候,两个算法几乎没有时间差异,但是当n比较大的时候显然O(n)的算法效率更高。
练习题:
判断:
1-1算法分析的两个主要方面是时间复杂度和空间复杂度的分析。(T)
解析:时间复杂度与空间复杂度是衡量算法好坏的标准。
1-2(N^2)*logN和N*logN^2具有相同的增长速度。(F)
解析:显然两个复杂度函数的增长速度不相同。
1-3 2^N和N^N具有相同的增长速度。(F)
解析:显然两个复杂度函数的增长速度不相同。
1-4 100logN是O(N)的。(T)
解析:这个题目老师给出的解释是,是用程序计算100*log(n),要用到泰勒展开,因此是O(n)的。
1-5 (NlogN)/1000是O(N)的。(F)
解析:根据时间复杂度的计算方法我们知道,应该是O(NlogN)的。
1-6 在任何情况下,时间复杂度为O(n^2) 的算法比时间复杂度为O(n*logn)的算法所花费的时间都长。(F)
解析:实际上O表示的是渐进时间复杂度,也就是说1000 * n^2的算法与1000000*n^2的算法都是O(n^2)的,因此我们可以知道,并不是时间复杂度为O(n2^) 的算法比时间复杂度为O(n*logn)的算法所花费的时间都长。
1-7 对于某些算法,随着问题规模的扩大,所花的时间不一定单调增加。(F)
解析:由于有些问题的特殊性,算法的复杂度可能是常数级别的也就是O(1)的,比如给出n个数输出第i个,可以根据数组下标直接访问。
选择:
2-1 下面代码段的时间复杂度是(n^(1/2)).
x=n; //n>1
y=0;
while( x≥(y+1)*(y+1) )
y++;
解析:因为每次y自增1因此我们只需要计算在程序结束时y的值,我们就可以知道运行多少次,显然最后一次执行y=n^(1/2)-1,那么也就是最后y=n^(1/2),因此我们可以得出时间复杂度是O(n^(1/2))。
2-2 下面代码段的时间复杂度是(O(N^4))。
if ( A > B ) {
for ( i=0; i<N*N/100; i++ )
for ( j=N*N; j>i; j-- )
A += B;
}
else {
for ( i=0; i<N*2; i++ )
for ( j=N*3; j>i; j-- )
A += B;
}
解析:对于存在分支的语句我们分别计算所有的分支,然后取最坏的情况。
1.A>B的时候,我们知道执行次数应该是计算之后我们知道这个分支的时间复杂度是O(n^4);
2.A<=B的时候,此时执行次数应该是(别看成n^2,实际上是n*2根n*3),计算之后我们知道这一个分支的时间复杂度是O(N^2)。
综上我们考虑最坏的情况应该是O(n^4).
2-3 下面代码段的时间复杂度是(O(n^(1/2)))。
int func ( int n )
{ int i = 0, sum = 0;
while ( sum < n ) sum += ++i;
return i;
}
解析:不难发现sum实际上是计算的等差数列求和(++i与i++的区别注意一下),我们假设执行次数为x我们可以知道
sum=当sum==n的时候,我们可以估算出时间复杂度应该是O(n^(1/2))。
2-4 下面代码段的时间复杂度是(O(NlogN))。
for(i=0; i<n; i++)
for(j=i; j>0; j/=2)//
printf(“%d\n”, j);
解析:可以计算基本执行次数最后我们得出时间复杂度应该是O(log(n!)),一时间没在选项当中找到答案,后来查了资料才知道还有等价无穷大这样的概念,实际上nlogn与log(n!)是等价无穷大,当n比较大的时候我们可以认为他俩是等价的,又因为在这里O表示渐进,那么渐进于log(n!)自然也就渐进于nlogn了。
注:该题目最内层(也就是加了"//"的那一层)循环,执行次数是logi,也就是每次取一半,多少次之后是1.
2-5 下面代码段的时间复杂度是(O(n^2))。
x=0;
for( i=1; i<n; i++ )
for ( j=1; j<=n-i; j++ )
x++;
解析:类似前面我们先计算基本执行次数最后我们可以知道时间复杂度就是O(n^2)。
2-6 要判断一个整数N(>10)是否素数,我们需要检查3到√N之间是否存在奇数可以整除N。则这个算法的时间复杂度是(O(n^(1/2)))。
解析:题目说的很清楚,要判断检查3到√N之间是否存在奇数可以整除N,那么最好的情况就是类似6,这样的上来就被3检查出来了也就是O(1),那么最坏的就是他是素数,要从3到√N全都遍历(只要奇数就行了),因此时间复杂度就是O(n^(1/2))。
注:这个题我们还是取最坏的情况。
2-7 下列函数中,哪个函数具有最慢的增长速度:
- N^1.5
- NlogN^2
- (N^2)logN
- N(logN)^2
解析:对四个函数求交点之后我们知道第二个复杂度函数在n较大的时候位于最下面。
2-8 给定N×N×N的三维数组A,则在不改变数组的前提下,查找最小元素的时间复杂度是(O(n^3)).
解析:对于数组求最大值,我们只需要全部遍历就好了,因为这个题说明不改变数组,如果能排序就得另说。
2-9 计算机算法指的是(解决问题的有限运算序列)。
2-10 计算机算法必须具备输入、输出和(可行性、确定性和有穷性)等五个特性。
难点:
1.知道时间复杂度与空间复杂度指的是什么。并且会计算两个复杂度。
2.对于常见的循环结构能够熟练的计算基本执行次数。
3.对基本概念进行必要的记忆。