寻寻寻寻20.11.30更新:
1、选入PTA部分时间复杂度例题。
2、排版调整。
0、前言:
一个程序在写出来之前是无法准确估计实际运行时间的。但是几乎所有算法竞赛的任务都会告知输入数据的规模和运行时间限制,这就允许选手通过分析算法的时间复杂度,从而事先估计能否在限定的时间内运行完程序。
一个程序的空间复杂度是指运行完一个程序所需内存的大小。利用程序的空间复杂度,可以对程序的运行所需要的内存多少有个预先估计。
一、时间复杂度
(1)表示方法
大O符号表示法中,时间复杂度的公式是: T(n) = O( f(n) ),其中f(n) 表示每行代码执行次数之和,而 O 表示受限于f(n),即与之成比例关系。这个公式的全称是:算法的渐进时间复杂度。
例子:
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
假设每行代码的执行时间都是一样的,我们用 1颗粒时间 来表示,那么这个例子的第一行耗时是1个颗粒时间,第三行的执行时间是 n个颗粒时间,第四行的执行时间也是 n个颗粒时间(第二行和第五行是符号,暂时忽略),那么总时间就是 1颗粒时间 + n颗粒时间 + n颗粒时间 ,即 (1+2n)个颗粒时间,即: T(n) = (1+2n)*颗粒时间,从这个结果可以看出,这个算法的耗时是随着n的变化而变化,因此,我们可以简化的将这个算法的时间复杂度表示为:T(n) = O(n)
(2)常见时间复杂度量级及其举例
时间复杂度排序进行求导即可
以下排序为算法复杂度增长率排序,从上到下依次增加。
1、对数阶O(logN)
int i = 1;
while(i<n)
{
i = i * 2;
}
在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。也就是说 2 的 x 次方等于 n,也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)
2、根方阶O(n1/2)
x=n; //n>1
y=0;
while( x≥(y+1)*(y+1) )
y++;
while循环共进行了p次循环,且满足p2=n;所以p=n1/2。
3、常数阶O(1)
无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1),如:
int i = 1;
int j = 2;
++i;
j++;
int m = i + j;
上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。
4、线性阶O(n)
最开始的代码示例
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。
5、线性对数阶O(nlogN)
线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)。
就拿上面的代码加一点修改来举例:
for(m=1; m<n; m++)
{
i = 1;
while(i<n)
{
i = i * 2;
}
}
6、平方阶O(n²)
平方阶O(n²) 就更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。
举例:
for(x=1; i<=n; x++)
{
for(i=1; i<=n; i++)
{
j = i;
j++;
}
}
这段代码其实就是嵌套了2层n循环,它的时间复杂度就是 O(n*n),即 O(n²)
如果将其中一层循环的n改成m,即:
for(x=1; i<=m; x++)
{
for(i=1; i<=n; i++)
{
j = i;
j++;
}
}
那它的时间复杂度就变成了 O(m*n)
立方阶O(n³)、K次方阶O(n^k)同理。
7、指数阶(2^n)
fun(int n){
if(n==0)return 1;
else return fun(n-1)+fun(n-1)
}
如上代码所示,n每增加一就会多嵌套一层fun函数,产生的结果是使目前运行总次数翻倍。实际上运行了(2 ^n -1)次, 可简化为2 ^n。
二、空间复杂度
既然时间复杂度不是用来计算程序具体耗时的,那么我也应该明白,空间复杂度也不是用来计算程序实际占用的空间的。
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用 S(n) 来定义。
空间复杂度比较常用的有:O(1)、O(n)、O(n²),我们下面来看看:
(1)空间复杂度 O(1)
如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)
举例:
int i = 1;
int j = 2;
++i;
j++;
int m = i + j;
代码中的 i、j、m 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1)
(2)空间复杂度 O(n)
我们先看一个代码:
int[] m = new int[n]
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,这段代码的2-6行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n)
三、PTA例题
知列入了我感觉易错且有代表性的题目,其余例题较易,或皆可由此延伸解决。
(1)判断题
1、N2 logN 和 N longN2 具有相同增长速度。F
增长速度即对N求导 ,二者大小不同,下题同。
2、2N 和 NN 具有相同增长速度。F
3、100logN是O(N)的。T
题目重述:即100logN该算法时间复杂度受限于N的大小,或者说是取决于N的大小。未知数只有一个一元N,且其数量级为1,所以说是对的,T。再来看下一题做出比较。
4、NlongN是O(N)的。F
题目重述:即NlogN该算法时间复杂度受限于N的大小,或者说是取决于N的大小。未知数为N原式可化为long(NN),为幂指级,所以答案应该是O(NN)。F。
5、在任何情况下,时间复杂度为O(n^ 2 ) 的算法比时间复杂度为O(n*logn)的算法所花费的时间都长。 F
没啥好说的,所花费的时间受N的大小,循环体代码量,数据类型,硬件等影响,F。
(2)选择题
1、下面代码段的时间复杂度是()。
x=n; //n>1
y=0;
while( x≥(y+1)*(y+1) )
y++;
答案:O(n1/2)
解析:while循环共进行了p次循环,且满足p2=n;所以p=n1/2。
2、下列代码
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;
}
解析:此时要分两种情况讨论,上循环体的循环次数是从NN到NN-NN/100的的等差数列之和,公差为1.算出得出复杂度为N4;下循环体是从N3-N2到N3等差数列之和,公差为1,得出时间复杂度为N,所以总时间复杂度受上循环体限制,为N4。
(吐血整理)
3、下列函数中,哪个函数具有最慢的增长速度:
A.N1.5
B.NlogN2
C.N2logN
D.N(logN)2
先求导,然后再根据上面第二部分,不同算法复杂度阶梯比较,B选项最慢