一直对算法的基础“时间复杂度” 的概念理解的不是很清楚,感觉摸棱两可,尤其遇到复杂的代码,分期时间复杂度简直就是一头雾水,现在腾出手来好好学习一下,将心得以及学习过程记录下来,供以后参考
1.名词解释:
在计算机科学中,算法的时间复杂度(Time complexity)是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的情况。【来源于维基百科】
2.分析
首先,最重要的,我们都知道,算法的复杂度都是假设在最坏的情况下的运行耗费时间,并且假设每一句简单的代码的运行时间都是相同的。
public static void main(string args[]){
int n = 100;
for(int m = 0;m<n;m++){
System.out.println("Hello World!");
}
}
从总简单的循环输出Hello World来说,总共有如下几句:
蓝色框运行时间为2个时间单位;
黑色框运行时间为n+1个时间单位(因为判断了n+1次);
红色框运行时间为n+n=2n个时间单位;
那么总体来说,这段代码运行时间就是3n+3个时间单位。运行花费的时间和我们的n成正相关。我们可以取运行总时间为T(n),
那么T(n)=3n+3。
因为我们总考虑最坏的情况,那么当n为10000,百万,或者无穷大的时候,这个式子其实就和3没有关系了,3并不能改变n 的数量级,这个式子就可以简写为T(n)=n
大部分情况下,我们会保留最高次项并忽略该项的系数。
比如:
T(n)=n+1 忽略常数项 T(n)~n
T(n)=n+n^2 忽略低阶项 T(n)~n^2
T(n)=3n 忽略最高阶的系数 T(n)~n
忽略低阶项,简单的来说,就是忽略当n非常大的时候,一个项相对于另一个项而言,影响非常小的项。一般而言,我们记住;
O(1)<O(logN)<O(N)<O(NlogN)<O(n^2)<O(n^3)<O(1)<O(1)<O(1)<O(1)<O(1)<O(1)
话说回来,我们化简完函数后,这个式子就是程序算法的时间复杂度了,我们记为O(f(n)),f(n)就是简化后的式子,比如说刚开始我们说的T(n)=3n+3,简化后T(n)~f(n)=n,即O(n).
更准确地说O代表了运行时间函数的一个渐进上界,即T(n)在数量级上小于等于f(n)
所以时间复杂度就可以表示某个算法的运行时间的趋势,大致地度量算法效率的好坏。那我们该如何算这个时间复杂度呢?
首先,我们先得出运行时间的函数,类似于前面的3n+3,第二步就是对函数进行简化,类似于前文的得到T(n)=n。
- 用常数1来取代运行时间中所有的加法常数
- 在新的函数公式中只保留最高阶项
- 如果最高阶项存在且不是1,则忽略这个项的系数
举个栗子:
int n = 100;
n = n+1;
System.out.println(n)
三句代码,每一行执行一次,单次时间为1,那么T(n)=3,简化后为1,则时间复杂度为O(1).
如果一个算法代码太多,上面这样算就显得有点傻。大部分时候我们可以耍耍小聪明。一般来说,最内部执行次数最多的语句就能决定整个算法的时间复杂度了。
for(int i = 0;i<n;i++){
System.out.println("HelloWorld");
}
如上的for循环,我们只需要关注最内层的语句执行次数的而规律就行了。很明显,这个print语句会随着问题规模n的增加会呈线性增加,与n正相关,则可以判定时间复杂度为O(n)。
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
System.out.println("Hello World")
}
}
很明显,双层for循环的时间复杂度为O(n*n).
int sum = 1;
while(sum<n){
sum = sum*2
}
这段代码的复杂度就厉害了,是对数级别的。每循环一次,sum就自乘2,什么时候跳出循环呢?不知道,假定为x,那么2^x=n,解出x=logn。这说明随着n的增大,最消耗时间的内层语句是呈对数变化的。
这么一讲是不是感觉简单了很多?此篇先告一段落。