你在北方的寒夜里四季如春,我在南方的艳阳里冻成傻逼,作为一名刚从北方放假回到南方的学生党,此时感觉受到了100t的伤害~(咳咳~不扯了,我在被窝的远方瑟瑟发抖的掏出小手写的这波帖子,求安(dian)慰(zan))。
书归正传哈,今天咱们要讲的就是算法的时间复杂度和空间复杂度,众所周知,算法是一个程序的灵魂,程序写的好与坏,关键在于它的执行效率要快,所占内存空间要小,因此要想对于某个问题得到最有效的算法,我们首先要去了解什么是算法的时间复杂度和空间复杂度,和如何计算一个算法的时间复杂度,并且如何优化。(传说中的终极三大步骤。是啥,为啥,该咋办)
一、算法的时间复杂度
1.什么是算法的时间复杂度
算法的时间复杂度就是在进行算法分析时,语句的总执行次数我们记为T(n),它是关于问题规模n的一个函数,我们就是分析T(n)随n变化的情况以及确定n的数量级。我们把算法的时间复杂度T(n)来进行算法的时间度量,记作T(n)=O(f(n))。其实f(n)是算法的准确时间复杂函数,但由于我们现实生活中,n一般非常大,所以我们不用考虑具体的函数形式(函数的渐进增长,因此判断一个算法的效率,更关注的应该是最高阶的阶数),而只需要去考虑其增长率就可以了,所以我们又把T(n)叫做算法的渐进时间复杂度,简称为时间复杂度。这样用O()来表示时间复杂度的记法,我们把它叫做大O表示法。
我们如何去寻求关于一个问题最有效率的算法呢,其实就是去寻求当n增长时,T(n)增长最慢的算法(你也可以看成一阶导=。=)。
2.如何推导大O阶方法
上面咱们已经讲过什么是O表示法,那么问题来了,对于一个n规模的算法,如何求解其大O表示法呢,我只简单谈一谈我的经验:
①得到关于问题的f(n)函数,并只保留最高阶
②用常数1来替代所有的系数常数
大O表示法得到的可分为常数阶、线性阶、对数阶、平方阶、立方阶、指数阶、阶乘阶等等,因为在正常开发中我们的n的规模都会很大,因此像立方阶以上的算法我们基本不予考虑,所以大家在设计算法时尽量不要选择平方阶以上的算法,因此我们只介绍常数阶、线性阶、对数阶以及平方阶。(福利:常用的时间复杂度所消耗的时间从小到大排序为O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n))
3.常数阶
常数阶的表示方法为O(1),下面举个非常简单的例子:
int sum = 0,n = 100;//执行一次
sum = n*(n+1)/2;//执行一次
printf("%d", sum);//执行一次
大家按照我的计算方式:首先得到f(n)=3,最高阶就是常数阶3,第二步,把常数变为1,就得到了此算法的算法复杂度为O(1),是不是灰常简单,其实其它阶的也可以这么算,不过对于有循环的,大家可以采取更加简便的方法进行运算(口算),预知详情,请往下看~
4.线性阶
什么叫做线性阶呢?其实就是一个f(n)是一个一元一次的函数,对应的大O阶为O(n),下面再举个例子:
int i;
for(int i = 0;i < n;i++){//n次
//时间复杂度为O(1)的程序步骤序列
}
上方的计算仍然可以按照我们的计算方法,非常简单的就能够算出来其为O(n),大家可能有疑问的就是时间复杂度为O(1)运算n次为啥就是O(n),其实非常简单,就是你想把它看成几就看成几,反正就是一个常数(不随n变化而发生变化的数),按照我们提供的方法,运算出来仍然为O(n),所以我们运算得时候就可以吧O(1)看成1,其它也如法炮制。
5.对数阶
给出下面一段代码,求其时间复杂度:
int i = 1;
while(i < n){
i = i*2;
}
这段代码非常简单,但有很多初学的同学会犯错误,就是看到有循环,没有嵌套,其就是O(n),这种算法是错误的,按照咱们的方法:先算f(n),2^x=n,f(n)=x=logn(有个以2为底,抱歉,打不出来=。=,大家明白什么意思就可以了),所以它复杂度为O(logn)
6.平方阶
平方阶O(n^2),其出现必然会有一个必要条件,就是循环嵌套:
特别简单的例子就不举了,举一个稍微复杂一丢丢丢丢丢的:
void function(int count){
int j;
for(j = count;j < n;j++){
//时间复杂度为O(1)的程序序列
}
}
对于这段代码:
n++;//1
function(n);//n
int i,j;
for(i = 0;i < n;i++){//n*(n+1)/2
function(n);
}
for(i = 0;i < n;i++){//n*(n+1)/2
for(j = i;j < n;j++){
//时间复杂度为O(1)的程序序列
}
}
f(n) = 1+n*(n+1) so T(n)=O(n^2)
时间复杂度小结:我们所求的时间复杂度均为最坏时间复杂度,其实我们所期望的复杂度是平均复杂度,也就是所谓的平均时间,但现实中数学期望需要建立合适的数学模型去求解,一般的现实问题很难通过分析得到,所以,一般没有特殊说明的话,我们所求的就是最坏时间复杂度。
二、算法的空间复杂度
其实我们写算法时,往往时间复杂度和空间复杂度是有一定关系的,往往时间复杂度较少的算法可能所占用的空间(也就是空间复杂度要稍微高一些)。举个简单的例子,就是我们在学c语言时都接触过算闰年的算法,如果我们选取一个非常大的数组来存储各个年份,闰年记为1,不是记为0,那么我们的时间复杂度肯定为常数阶,但是可取么,因为我们浪费的空间非常大(也就是空间复杂度很大),所以,怎样去选择合适的算法,需要综合考虑。
算法的空间复杂度需要通过计算算法所需的存储空间来实现,其公式记为S(n)=O(f(n)),f(n)为关于n的所占存储空间的函数。
一般情况下,一个程序在机器上执行的时候,除需要存储程序本身的指令、常数、变量和输入数据之外,还要存储对数据操作的存储单元。若输入数据所占空间与算法本身没有关系,只取决于问题的本身,此时,只需要分析算法在实现的时候的的辅助单元所占用的存储空间即可,要是其辅助空间对于输入数据量而言是个常数,此算法成为原地工作,空间复杂度为O(1)。
一般的呢,我们用时间复杂度来度量运行时间的需求,空间复杂度来说明存储空间的需求。当然,我们计算一个算法的效率时重点还是要考虑时间复杂度的问题(从我写的篇幅就能看出来),祝大家代码快乐,回被窝睡觉了...喜欢记得点赞留言转发哦,不对的地方欢迎大家批评指正~~~