一、阅读须知 :
- 文章面向的群体:
- 这个系列文章是面向和我一样的初级小白, 所以在文章中不会出现特别多的专业性的词汇, 如果有也会详加说明。
- 文章深度:
- 本系列文章是小白文, 所以基本没有深度,笔者也是在学习算法的道路上, 只不过是借助掘金平台分享自己的所得。
- 阅读本篇文章你会了解到:
- 什么是复杂度 ?
- 什么是大 O 表示法 ?
- 时间复杂度和空间复杂度是怎样计算的 ?
- 总结经验性结论, 得以快速判断出复杂度。
其实, 扯到算法这个层面上。说到底还是没有脱离数学😂
二、娓娓道来 :
1. 什么是复杂度 ?
-
复杂度是当程序某些变量变化的时候程序消耗资源的增长率。
怎么来理解这句话呢 ? 我们借助一段代码来理解这句话的含义。function computedSum(params) { let sum = 0; for(let i = 0; i < params.length; i++) { sum += params[i]; } return sum; } const arr = [1, 2, 3]; console.log(computedSum(arr));这是一段简单的求和代码片, 代码中可以看出 params 这个形参变量, 它的内容决定着
computedSum这个函数执行的系统时间以及消耗的内存空间, 换句话说, params 这个形参变量也决定着这个计算求和的程序运行时所消耗的资源的增长率,而这个增长率就可以用复杂度这个词语来表示了, 也即复杂度越大, 程序运行时所消耗资源的增长率就越大, 反之就越小。 -
复杂度是一个关于输入数据量 n 的函数。
怎么来理解这句话呢 ?- 其实上面我们已经提到过了, 这里的
n就相当于上面示例代码中的computedSum函数中的形参变量params, 而复杂度呢又与n有正向关系, 这里复杂度可以使用函数形式来表示。 - 如果一段代码的复杂度是 fn, 那么表示的时候应该这样表示 : f(n) => O(f(n))
- 其实上面我们已经提到过了, 这里的
2. 什么是大 O 表示法 ?
- 大 O 表示法, 就是用来描述一个算法最差的情况。
- 常见的表示方法 :
- O(1) 表示的是, 复杂度与计算实例个数 n 无关。
- O(n) 表示的是, 复杂度与计算实例的个数 n 线性相关。
- O(logn) 表示的是, 复杂度与计算实例的个数 n 对数相关。
- O(n²) 表示的是, 复杂度与计算实例的个数 n 平方相关。
- O(2ⁿ) 表示的是, 复杂度ui计算实例的个数 n 指数相关。
这块不太懂没有关系, 后面看到示例代码就会好理解些💪【不要放弃, 就快胜利了】。
3. 时间复杂度和空间复杂度是怎样计算的 ?
三条原则 :
- 复杂度与具体的常系数无关【例如: O(n) 与 O(2n) 表示的是同一个复杂度, 因为常系数 2 被忽略了】
- 多项式级的复杂度相加的时候, 选择高者作为结果【例如: O(n²) + O(n) 与 O(n²) 表示的是同一个复杂度, 因为多项式级的复杂度相加的时候, 选择高者作为结果, n² 比 n 大所以 O(n) 被舍掉】
- O(1) 表示一个特殊的复杂度: 表示资源消耗的增长率与输入数据量 n 无关【程序处理 10 数据需要消耗 2 个单位的时间资源, 3 个单位的空间资源。那么处理 100 条数据需要消耗同样量级的资源】
-1). 针对时间复杂度为 O(1) 的算法 :
function plus(params) {
return ++params; // 【1】
}
console.log(plus(10));
- 这种算法的时间复杂度就是 O(1), 因为随着输入数据量
n的增长++params都只执行一次, 也即++params运行的时间与n无关。 - 推导过程如下: 1 => O(1)
-2). 针对时间复杂度为 O(n) 的算法 :
function reverse(params) {
let len = params.length; // 【1】
let arr = new Array(len); // 【1】
for(let i = 0; i < len; i++) { // 【n】
arr[i] = params[i];
}
for(let i = 0; i < len; i++) { // 【n】
arr[arr.length - 1 - i] = params[i];
}
return arr; // 【1】
}
let arr = [1, 2, 3];
console.log(reverse(arr));
- 这种算法的时间复杂度就是 O(n), 因为根据
复杂度与具体的常系数无关原则可知, 并且随着输入数据量n的增长, 这两个顺序的 for 循环依次多执行一次, 这种线性相关的复杂度就可以用 O(n) 来表示。 - 推导过程如下: 1 + 1 + O(n) + O(n) + 1 => 3 + O(n + n) => O(2n) => O(n)
- 当然这里的空间复杂度为 O(n), 因为程序指定开辟了一个新的数组, 且数组的大小变化与输入数据量
n成正相关。
-3). 针对时间复杂度为 O(log(n)) 的算法 :
function search(params, num) {
let low = 0; // 【1】
let high = params.length - 1; // 【1】
while(low <= high) { // 【(n/2)^k】
let val = parseInt((low + high) / 2);
console.log('计数');
if(num === params[val]) {
return val;
}else if(num < params[val]) {
high = val - 1;
}else if(num > params[val]) {
low = val + 1;
}else {
return -1;
}
}
}
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(search(arr, 9));
- 这种算法的时间复杂度就是 O(log(n)), 这个会涉及到对数的概念, 对数就是幂运算的逆运算。23 = 8;这个就是幂运算, log28 = 3;这个就是幂的逆运算。如果这块理解了, 那么上面那段示例代码应该就可以理解了。那段示例代码每次迭代的次数都是上一次迭代次数的 1/2, 二分法在于不断细分, 分到 1 的时候就结束了。
- 推导过程如下【设二分查找需要 k 次】 : (1/2)k * n = 1 -> n = 2k -> k = log2n -> O(log(n))
-4). 针对时间复杂度为 O(n2)的算法 :
void(() => {
function reverse(params) {
let arr = [...params]; // 【1】
for(let i = 0; i < arr.length - 1; i++) { //【n】
for(let j = 0; j < arr.length - i - 1; j++) { // 【n * n】
if(arr[j] < arr[j + 1]) {
arr[j] = arr[j] ^ arr[j + 1];
arr[j + 1] = arr[j] ^ arr[j + 1];
arr[j] = arr[j] ^ arr[j + 1];
}
}
}
return arr; // 【1】
}
let arr = [1, 2, 3];
console.log(reverse(arr));
})();
- 这种算法的时间复杂度就是 O(n2), 因为当输入数据量
n增大的时候, 循环会循环 n * n 次。 - 推导过程如下: 1 + O(n * n)+ 1 => O(n2)
相信经过上面的介绍你已经可以初步对算法感觉不是那么的抵触或恐惧了, 因为这仅仅是 Hello World !
4. 总结经验性结论, 得以快速判断出复杂度。
-> 时间复杂度与代码的结构有着非常紧密的关系
-> 空间复杂度与数据结构的设计有关
经验性结论 :
- 一个顺序结构的代码, 时间复杂度是 O(1)。
- 二分查找,或者说采用分而治之的二分策略, 时间复杂度都是 O(log(n))
- 一个简单的 for 循环的时间复杂度是 O(n)。
- 两个顺序执行的 for 循环, 时间复杂度是 O(n) + O(n) = O(2n), 也就是 O(n)。
- 两个嵌套 for 循环的时间复杂度是 O(n2)。
5. 算法复杂度在真实场景中的应用
一个网站应用, 假设在一分钟内会收到 10 万次请求, 那么设每个访问的时间间隔是 t, 如果程序无法在 t 时间内处理完单次请求, 那么这个请求就会堆积下来, 长此以往系统会承受不住负载而瘫痪。这就需要在代码结构以及数据结构优化的层面入手, 降低程序运行的时间复杂度与空间复杂度, 以此来提高网站的抗压能力。
6. 算法复杂度简单的示例体验 :
计算机要处理 1 万条数据。
- 复杂度为 O(n) 的程序计算的次数在 1 万次左右。
- 复杂度为 O(n2) 计算次数是 1 亿次左右。
- 复杂度为 O(log(n)) 计算次数在 26 次左右。
所以在写代码的时候, 要有意识的思考采用什么样的代码结构, 使用什么样的数据结构能将程序的性能提高。
三、后语 :
- 以上就是这一个小章节对于算法入门的初步概述, 在这一章里, 我们了解到什么是算法复杂度、什么是大 O 表示法、如何推导时间复杂度与空间复杂度、总结常规规律, 便于快速判定算法的复杂度等 希望您从头看下来能有一份自己的收获,毕竟我们已经成功了一步, 不是吗 ?👌
- 本文如有谬误请及时在下方留言, 笔者会在第一时间更正。
- 最后在祭出一张笔者在网上找的时间复杂度的图 :