如何判断一堆代码是否够简化,是否还有优化空间,除了在代码的写法上的优化外(比如把 if else 写成自己都看不懂的超长三目),另一种方法就是判断时间复杂度与空间复杂度,采用一定的方法为代码优化提供理论基础。
(本文是对极客时间学习的总结)
日常惯例,先来看一波什么是大O表示法吧; 据百度词条可知:
词条上说了大O表示法大概的概念,注意上述最后一句,当输入量n逐渐增大时,时间复杂度的极限情景称为算法的“渐进时间复杂度”,简单来说如果在输入量n很小,或者输入量n最大值已知的情况下,大O表示法大的并不一定在时间上就运行地慢,所以大O表示法只是一个 “渐进时间复杂度” 的概念,在写代码是可以提供一定的参考价值,并不一定十分准确,很多时间具体情况需要具体分析。
既然我们认识了什么是大O表示法,那接下来就先看一看有哪些层级:
直接上图:
我们先从第一个说起。
先看一个例子:
let a = 1;
let b = 2;
let c = a + b;
上述代码中每一行都是一个次执行时间,所以以上代码的时间复杂度是O(1+1+1),所以代码的复杂度是O(1)。
大O表示法表示的是代码执行时间随数据规模增长的变化趋势,所以当n无限大时,可以忽略公式中的常量、低阶、系数三部分。
再看下一段代码
function cal(n){
let sum = 0;
for (let i=0; i<n; i++) {
sum += i;
}
return sum
}
代码中第二行只执行了一次所以复杂度是O(1),第三四行代码执行了n次,所以复杂度就是O(n),那么代码总体的复杂度就是O(1+n+n),我们去掉常量和低阶,这段代码的时间复杂度就是O(n)。
再来看
function cal(n){
let sum = 0;
let sum1 = 0;
for (let i=0; i<n; i++) {
sum += i;
for (let j = 0; j<n; j++){
sum1 += j
}
}
return [sum,sum1]
}
代码块中第二三行都是执行一次,复杂度都是O(1),第四五行执行了n次,复杂度是O(n),第六七行,执行了O(n*n)次,就是O(n²),所以以上代码的复杂度就是O(1+1+n+n+n²+n²),去掉常量和低阶,代码的复杂度就是O(n²)。
对于比较复杂的函数,分析方法是同样的。看下面代码
function cal(n){
let sum = 0;
let sum1 = 0;
for (let i=0; i<n; i++) {
sum += i + f(i);
}
return sum
}
function f(n){
let sum = 0;
let sum1 = 0;
for (let i=0; i<n; i++) {
sum += i;
}
return sum
}
代码块中如果不看f(i),cal()函数只是一个普通的函数,第四五行就代表他的时间复杂度O(n),因为f(i),也是一个函数,所以第五行的复杂度不是O(n)而是O(n*n),所以四五行总的复杂度就是O(n+n*n),所以代码总的复杂度就是O(1+1+n+n²),最后得到的就是O(n²)。
上面是几种最常见的复杂度例子,下面我们来看一点不一样的。
for (let i = 1; i<=n; i*=2) {
console.log(i)
}
代码块中只有一个简单的循环,原本其复杂度应该是O(n),但是因为条件从i++改为了i*=2,所以代码中的i的值就是一个等比数列
2º 2¹ 2² 2³ ...2^k...2^x = n,所以我们只需要知道x就知道代码的循环次数了。2^x = n,所以x=,所以时间复杂度就是
,我们去掉其中的常数,最后的时间复杂度就是O(logn)。
上面代码的复杂度是O(logn),如果我们按照这个方法将他循环n遍,那么他的复杂度就是O(n*logn),最后得到的结果是O(nlogn),这就是O(nlogn)的由来了。
代码的时间复杂度暂时就总结到这,后续还有最好情况时间复杂度,最坏情况时间复杂度、平均情况时间复杂度和均摊情况时间复杂度。下次更新再总结。
提一嘴
function select(word, arr){
for(let i=0; i<arr.length; i++){
if(arr[i]==word) return i
}
}
上面代码块中如果word刚好在数组第一个位置,那么循环在第一次的时候结束的时候就退出了,那么上面代码复杂度就是O(1),如果在数组的第n个位置数组循环了n次,那么复杂度就是O(n),所以这就是最好情况时间复杂度和最坏情况时间复杂度。