前言
本章介绍我个人怎么理解 时间复杂度 以及 空间复杂度等算法基础知识,不定时补充
// ... 这里应该啰里八嗦说些废话
时间复杂度
概念
时间复杂度顾名思义就是定性的描述一个算法的运行时间。
时间复杂度常用大O来表示, 我们常见到 O(n) O(n^2) O(logn) O(nlogn) O(1)等等
在表示的过程中呢注意乘积的常数是可以省略的哈 比如 O(2n) = O(n) O(log2n) = O(logn)
怎么理解?
其实很简单,我们直接上代码
// 有这么一个数组 nums, 长度是6
const nums = [1, 2, 3, 4, 5, 6]
// 第一层循环 对nums做一次遍历的操作 那么这时候时间复杂度就是 O(n) => n = nums.length
for (let i = 0; i < nums.length; ++i) {
// do something..
// 假设你需要在遍历nums的时候由于某种原因需要再遍历多一层 比如你想让当前数字和其他数字挨个比较
// 那么好家伙,你这个函数就有双重的遍历 你的时间复杂度提升到了 O(n^2)
for (let j = 0; j < nums.length; ++j) {
// do something..
}
}
从上述代码来看是不是很容易就理解了O(n) 和 O(n^2)的区别呢?
那么问题1来了
// 这段代码的时间复杂度是多少?
// 还是这个数组 老朋友了
const nums = [1, 2, 3, 4, 5, 6]
for (let i = 0; i < nums.length; ++i) {
// do something
}
for (let i = 0; i < nums.length; ++i) {
// do something
}
答案是 O(n) n = nums.length
注意,这里的循环他不是嵌套的
问题2速度很快啊,立刻就追上来了
// 这段代码的时间复杂度是多少?
// 又是这个数组 老老朋友了 还带来了一个新朋友nums2 nums 长度为 6 , nums2 长度为 5
const nums = [1, 2, 3, 4, 5, 6], nums2 = [5, 4, 3, 2, 1]
for (let i = 0; i < nums.length; ++i) {
// do something
// 注意这里遍历 nums2 了
for (let j = 0; j < nums2.length; ++j) {
// do something..
}
}
答案是 O(nm) n = nums.length, m = nums2.length 你说对了吗?
其实很容易理解,O(n * n) = O(n^2) 嘛
接下来告诉你什么是 O(logn), 不多说直接代码见
// 这段代码就是让 count 不断乘 2 去逼近 n 直到比 n 大
let count = 1
while (count < n) {
count *= 2
}
用数学来说 假设 count 在 x次乘 2 之后终于比 n大了跳出循环 可以得到公式 2^x = n 即 x = log2n
2是常数,所以可以简写掉也就拿到了 O(logn)
问题3虽迟但到啊!!那什么是O(nlogn)?
你看这个其实也能明白就是 n * logn 了嘛,比较常见的例子就是我们的排序算法 归并排序,快速排序的一个平均时间复杂度
qio duo ma de!!怎么又来一个平均时间复杂度的概念了!!
我先解释一下哈 为啥这两个排序都是O(nlogn)
首先这两个排序使用的都是一个分治的思想,分治嘛分而治之,其实也就是递归的去处理也就是logn的操作啦
那n哪里来的,在归并排序中,合并每个子问题的解,才能得到原问题的解,这个操作其实就是n(外层循环)
在快速排序中,抽出一个数后需要遍历其他数(比他小的放左边,比他大的放右边),那这就是n
(描述的如果觉得不够清晰的话,欢迎查看我的另一篇详细介绍数组排序算法的文章)
回到上面这个平均时间复杂度,其实字面意思就是这个算法运算时间的平均时间,那有平均就会有最好和最坏
所以你偶尔能看到最好时间复杂度/最佳时间复杂度,指的就是这个算法刚刚好就不用跑完提前就可以结束掉了,打个比喻,你要做一张试卷,做了第一题之后发现后面的题都被大佬当成练习卷写完了 第一题so easy大佬不屑于写,那你就很舒服了嘛
那么最坏时间复杂度/最差时间复杂度就相当于,倒了血霉了,大家可能这张卷子只要做一半,你要做完全部
时间复杂度比较图
附一张时间复杂度的比较图,可以看到O(1) yyds,越往上越拉垮,所以在使用嵌套循环的时候要多考虑能不能优化哦,哪怕简单的剪枝也是优化不是吗
空间复杂度
概念
空间复杂度就是指一个算法在运行过程中临时占用的存储空间的大小。
好巧啊他也是用O来表示
好巧啊他常数系数也可以省略。
怎么理解?
一句话描述: 空间复杂度是看函数中创建对象的个数
直接结合代码来看看吧
const a = 1, b = '3', c = 5
这种情况呢我们能看到定义了三个常量, 空间复杂度是 O(3 * 1) = O(1)
// how old are you? 怎么老是你?
const nums = [1, 2, 3, 4, 5, 6]
那么这种情况定义了一个一唯数组 (注意这个一维,待会要考的),这个空间复杂度就是O(n) n = nums.length
// 哟老朋友不一样了
const nums = [[1], [2], [3], [4]]
那么这种情况定义了一个二唯数组 (注意这个一维,待会要考的),这个空间复杂度就是O(n^2) n = nums.length
那么以此类推 三维数组就是 O(n^3)...
敲黑板了!要注意 递归函数
当算法涉及到递归的时候,由于每次递归都是访问当前这个函数,又会重新创建一遍变量,所以
递归的深度就是空间复杂度的最大值 如 O(n) n = depth
问题:又有递归又有数组我咋算?
取两者之间更大的那个!!
总结
来一波小总结,当前算法着重要注意的是时间复杂度,时间复杂度优先级比空间复杂度更高,由于现在计算机的内存管够,所以一般采用的就是以空间去换时间的做法。