如何衡量代码好坏,算法的考察到底是在考察什么呢?
衡量代码好坏有两个非常重要的标准就是:运行时间和占用空间,就是我们后面要说到的时间复杂度和空间复杂度,也是学好算法的重要基石。
确切的占内用存或运行时间无法进行计算,而且同一段代码在不同性能的机器上执行的时间也不一样,可是代码的基本执行次数,我们是可以算得出来的,这就是时间复杂度。
平时表示算法复杂度主要就是用 O(),读作大欧表示法,只用一个 O()
表示。
时间复杂度
O(1)
我们都知道,js 的执行是从上往下顺序执行的,for循环等会影响代码执行的速度。
看一下这个代码:
function foo() {
let number = 0;
console.log(number);
number++;
console.log(number);
}
就是很简单的顺序执行代码,完成这些代码需要4个步骤,但是在计算机的世界里,常数1和常数100没有什么区别,因此
- 如果只是常数直接估算为1,O(3) 的时间复杂度就是
O(1)
,不是说只执行了1次,而是对常量级时间复杂度的一种表示法。一般情况下,只要算法里没有循环和递归,就算有上万行代码,时间复杂度也是O(1)
相应地,如果代码里有 if 的判断,也可以看作常量,因为它的执行次数不会随着任何一个变量的增大而变长。
O(n)
再来看第二段代码:
function foo() {
let arr = [1, 2, 3, 4, 5]
for(let i = 0; i < arr.length; i++) {
console.log(i)
}
}
里面有一个for循环,循环是 n 次,每次循环里都会执行:i < arr.length i++ console.log(i)这三句,也就是说,循环执行次数是3n,加上 arr = [1, 2, 3, 4, 5] i = 0
刚刚提到常数对时间复杂度的影响可以忽略不计,所以3n 和 n 的差别不大,可以看作 n ,几句上下文的执行也可忽略不计,所以一层循环的时间复杂度就是 O(n)
O(n²)
比如嵌套循环,如下面这样的,里层循环执行 n 次,外层循环也执行 n 次,总执行次数就是 n x n,时间复杂度就是 n 的平方,也就是 O(n²)
。假设 n 是 10,那么里面的就会打印 10 x 10 = 100 次:
function foo(n) {
for(let i = 0; i < n; i++) {
for(let j = 0; i< n; j++) {
console.log(i+j)
}
}
}
再来看这段代码:里面包含一层的循环,也包含两层的循环,我们取最高次项,所以它的时间复杂度也是 O(n²):
function foo(n){
if( n > 100){
for( let k = 0; k < n; k++){
console.log(k)
}
}else{
for( let i = 0; i < n; i++){
for( let j = 0; j < n; j++){
console.log(j)
}
}
}
}
O(logn)
这段代码里,循环次数的影响主要来源于 n/2 ,这个时间复杂度就是 O(logn)
function foo(n){
let day = 0
while(n > 1){
n = n/2
day++
}
return day
}
console.log( foo(16) )
function foo(n){
for(let i = 1; i <= n; i *= 2){
console.log(i)
}
}
foo( 16 )
随着数据量或者 n 的增大,时间复杂度也随之增加,也就是执行时间的增加,会越来越慢,越来越卡。总的来说时间复杂度就是执行时间增长的趋势,那么空间复杂度就是存储空间增长的趋势。
空间复杂度
空间复杂度是衡量算法在运行过程中临时占用存储空间大小的度量。
空间复杂度记作S(n)=O(f(n)),其中n是问题规模,f(n)是某个函数。
空间复杂度不是程序本身的大小,而是程序运行时占用的内存空间大小,它包括存储算法本身所需的存储空间、算法的输入输出数据所需的存储空间和算法在运行过程中临时占用的存储空间。算法的输入输出数据所需的存储空间是由问题本身决定的,通过参数列表由调用函数传递进来,这部分空间不随算法的不同而改变。而算法本身占用的存储空间和算法的书写长度成正比,这部分空间可以通过编写更短的算法来压缩。
算法在运行过程中临时占用的存储空间是衡量空间复杂度的关键部分,它主要包括为局部变量分配的存储空间,如参数表中的形参变量和函数体中定义的局部变量。
此外,空间复杂度与时间复杂度是相辅相成的,在追求较好的时间复杂度时可能会增加空间复杂度,反之亦然。因此,在设计算法时,需要综合考虑算法的各项性能,包括使用频率、处理的数据量大小、编程语言的特性、运行机器的系统环境等因素。
常用的空间复杂度有 O(1)
、O(n)
、O(n²)
O(1)
只要不会因为算法里的执行,导致额外的空间增长,就算是一万行,空间复杂度也是 O(1)
,比如下面这样,时间复杂度也是 O(1)
function foo() {
let number = 0;
console.log(number);
number++;
if(number === 1) {
console.log(1)
}
console.log(number);
}
O(n)
比如下面这样,n 的数值越大,算法需要分配的空间就需要越多,来存储数组里的值,所以它的空间复杂度就是 O(n)
,时间复杂度也是 O(n)
function foo(n) {
let arr = []
for( let i = 1; i < n; i++ ) {
arr[i] = i
}
}
O(n²)
O(n²) 这种空间复杂度一般出现在比如二维数组,或是矩阵的情况下
不用说,你肯定明白是啥情况啦
就是遍历生成类似这样格式的:
let arr = [
[1,2,3,4,5],
[1,2,3,4,5],
[1,2,3,4,5]
]