从斐波那契数列了解时间复杂度
引入斐波那契算法
说到菲波那切数列(给定一个数组,某一项的数都等于前一项的数与前前一项的数的和),我们首先想到的解法就是使用递归函数进行一个调用,当执行到首项和第二项时,我们就对其值进行返回。凡是要获取大于等于第二项的,我们通通还要进行递归直到执行至第一第二项的时候。下面我们直接上代码。
public static int fei (int n){
if(n<=1) {
return n;
}
return fei(n-1) + fei(n-2);
}
所带来的的问题:
当要叠加的斐波那契数超过一定的范围时,程序会发生卡顿并且有可能会带来虚拟机的栈溢出(因为疯狂的递归调用方法,程序不断的发生压栈而不释放的操作导致的栈溢出)
修改后的代码
public static int fei2 (int n){
if(n<=1) {
return n;
}
/*能到这部的都是大于等于1的数*/
int first = 0;
int second = 1;
for (int i = 0; i < n - 1; i++) {
int sum = first + second;
first = second;
second = sum;
}
return second;
}
优化剖析
使用了一个for循环对斐波那契规律的一个解决。根据规律我们不难发现,前两个数的加和的第二次进行加法运算的两个数的第二个数。这句话我们有点难理解,画个图就不难明白:
算法的评估
首先,对算法的评估不能从其代码量进行评估。就例如上个例子中的斐波那契,使用了递归算法也就一句话,但是远不如一个for循环的性能要好。
如果从效率上评估的话,对相同数据的处理时间是我们最能想到的一个方法,该方法也叫事后统计法。该方法存在许多确定:
1、不确定的因素(一些硬件设备带来的影响,例如CPU,内存等等)。
2、必须编写测试代码进行运行测试
3、测试数据难以保证公正性;数据不同带来的反应也不同,例如一个求和累加函数当数值较小时可能会比性能好的代码所需的时间要短。
所以一般用一下维度进行算法的评估
1、正确性
2、可读性
3、健壮性(对不合理的输入的反应能力和处理能力)
4、时间复杂度:估算程序指令的执行次数(执行时间)
5、空间复杂度:估算所需占用的存储空间
时间复杂度
通常表现为程序所要执行的次数,一个分号代表执行了一次,也就是要执行多少条语句。一般用大O表示法来描述时间复杂度,它表示的数据规模n对应的复杂度。
使用方法:忽略常数,系数,低阶
1、忽略常数
例如该程序需要执行150条或者250条语句,其时间复杂度都用O(1)来表示。因为不管是150还是250,程序所要执行的语句都是常数条。
2、忽略系数
当程序要执行的语句条数为an-b时,其时间复杂度是O(n)忽略掉常数b和系数a,当n大到一定常数的时候,常数可被忽略。
public static void timeN(int n) {
// O(n)
// 1 + 3n(判断执行n次,i++执行n次,内容体也执行n次)
for (int i = 0; i < n; i++) {
System.out.println("test");
}
}
3、忽略低阶
当程序要执行的语句条数为an^2 + bn +c时,其时间复杂度是O(n^2)。忽略系数a和后面所有的子项。当n大到一定常数的时候,其剩余项对平方项的影响可忽略不计。所以,当有更好阶的以更高阶的来计算,忽略掉后面的所有剩余项。
4、对于对数
不管其底数是多少,其用大O表示法的时间复杂度都是logn
log2 (n)
public static void test5(int n) {
// 8 = 2^3
// 16 = 2^4
// 3 = log2(8)
// 4 = log2(16)
// 执行次数 = log2(n)
// O(logn)
while ((n = n / 2) > 0) {
System.out.println("test");
}
}
log5()
public static void test6(int n) {
// log5(n)
// O(logn)
while ((n = n / 5) > 0) {
System.out.println("test");
}
}
原理:log2(n) = log2(9) * log9(n) (使用换底公式进行换算: loga(b)=logc(a)/logc(b) );常数项可忽略所以log2(n)也可等价于log9(n),所以不论底数为多少时间复杂度都相同,最终都用logn来表示对数的时间复杂度。
大O表示法仅仅是一种粗略的分析模型,是一种估算近似值,但可以很好的在短时间内了解一个算法的执行效率。
5、常见复杂度
复杂度之间的大小比值:
PS:可通过函数生成网站对比各个时间复杂度的情况: https://zh.numberempire.com/graphingcalculator.php
空间复杂度
估算该程序大概需要多少内存空间的一个指标数据;一般通过看该算法变量申请了多少内存空间进行评判,随着硬件的提升,程序的性能大部分都比较关注于时间复杂度。
复杂度的优化
衡量指标:
1、最小的空间
2、最少的时间
优化方式:以空间换时间、以时间换空间(具体要结合例子来说明)
斐波那契算法时间复杂度分析
使用递归:
以传入5为例(计算第五个菲波那切数)
从图中我们可以看到,递归的函数每次都要执行冗余的方法,相同的方法执行了好多次。我们看其时间复杂度:每次调用递归就返回前两个数相加,一加就调用一次所以时间复杂度就去了1。所以上图的例子就可以反映出执行的代码的次数(也就是15次),最终得出结论2^(n-1) -1,用O表示法也就是O(2^n)次。
优化后
优化后只需要一个循环即可解决了问题,并且循环次数和所求项(n)有关。所以,使用循环优化过后的代码时间复杂度降到了O(n),大大的节约了时间成本(主要是减少了CPU的运算)。