「算法与数据结构」时间与空间复杂度
Posted 不正经的前端
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「算法与数据结构」时间与空间复杂度相关的知识,希望对你有一定的参考价值。
写在前面
可能有些人会吐槽,学算法有什么用,顶多就是去面试大厂的时候能用上,大厂面试算法也只是强中筛强的一个敲门砖而已,我又不去面大厂,不用学它,真的是这样吗?
肯定不是,在计算机行业发展,不管是前端亦或是后端,算法都是进阶的一个绊脚石,可以说不会算法永远也成不了一个合格的高级工程师,想要进大厂确实要会些算法,但是它并不只是为了面试,它和我们的程序是息息相关的,有人说前端不需要算法?你把大名鼎鼎的 虚拟DOM (Virtual DOM)
置于何地,它就是 算法与数据结构
的一种体现,可能又有人会说了,我又不写虚拟DOM。。。嗯,那你总想要赚钱吧,走技术路线想拿高薪,算法是基石
网上有很多算法文章以及课程,说 7 天学会算法数据结构,30 天掌握算法与数据结构等等等等,别傻了,算法没有捷径,只能靠我们一点一点累积,你要做的首先就是相信自己不是天才,其次是每天坚持刷题,时间紧可以一周刷四五题,最好速度是每天一题,后期时间充裕或者熟练了甚至可以一天刷几题,即使很聪明的人在一段时间不接触算法后,还是会忘掉,所以重要的是持续下去
接下来我们来从零开始学习算法吧,如果你未曾刷过算法,这篇文章会带你了解什么是算法,什么是数据结构,在刷算法的同时,我们要明确自己的解法是否足够优质,而判断优质解发会通过时间以及空间两个维度来体现,本文更会重点介绍,这些都是刷算法之前需要必备的知识,如果能为你的算法之路带来启蒙,那真是太棒了,十分荣幸,如果你已经了解了这些知识,那么不妨快速阅读下,看看是否能为你的算法知识做些扩充,当然如果有错误的地方,你也可以为我指引,更是十分荣幸
什么是数据结构
数据结构其实就是是程序存储组织数据的方式,一个数据结构是由程序中的数据元素按照某种逻辑关系组织起来的,是若干个数据元素的组合
数据结构是程序中处理数据的基本单位,在程序中作为一个整体来使用
例如一个数字 1 或者一个字符 A
,它是一种数据结构
例如一个日期 2020年12月14日
,它由年月日这 3 种格式组成,也是一种数据结构
例如一个数组 [1, 'a', '2020年12月14日']
,它是由数字、字符、日期格式组合而成,也是一种数据结构
通俗来说,用一定格式组成的数据都是数据结构,我们比较常见的数据结构有字符串、数组、对象、堆、栈、链表、树、哈希表等等,你可能对着其中的一些数据结构并不了解,不要担心,你并不需要立刻知道它们都是什么,对于前端来说,我们使用 javascript
这个令我们又爱又恨的语言,它本身就存在的数据结构很少,那么对于像链表、树等等这些结构都是通过对象来模拟的,这点要先知道
在许多程序设计当中,数据结构的选择是一个基本的设计考虑因素,系统实现的困难程度和系统构造的质量都严重依赖于是否选择了最优的数据结构,选择最优的数据结构能够有效的提高运行的效率和节约存储空间的使用,但是要知道,没有十全十美的数据结构,每种数据结构都有局限性同样也有优点,要根据需求来选择合适的数据结构
什么是算法
什么是算法,我们都知道 1+2+3=6
,为什么等于 6 呢,你可能会说,1 加 2等于 3,两个 3 相加等于 6,这是小学生都会的数学常识,它就是广义上的算法
算法其实就是解决一个问题的完整步骤描述,是指完成一个任务准确而完整的步骤描述
算法的设计很多时候需要取决于数据结构,而算法的实现更依赖于采用的数据结构
提出一个问题的算法是一个从抽象到具体的过程
-
分析问题,选择数据结构,得出初步的解决方法
-
将解决方法合理地拆分,整理成许多步骤
-
为重复的步骤选择合理的循环变量
-
使用易转化为程序实现的自然语言简练地描述算法
了解了什么是算法之后,我们来看时间和空间复杂度,衡量不同算法之间的优劣我们通常从两个维度来考究
-
时间维度:指执行代码所消耗的时间,即时间复杂度 -
空间维度:指执行代码所消耗的空间,即空间复杂度
接下来就开始逐步剖析时间和空间复杂度了,先说时间复杂度
时间复杂度
在说时间复杂度之前,我们首先要理解一个概念即代码执行次数,也可称之为语句频度或时间频度,用 T(n)
表示
我们用例子来一步一步阐述,首先我们来看函数 fn1
function fn1(){
console.log("句末")
console.log("isboyjc")
}
我们来看这个函数中的语句会执行多少次
很明显此函数内部只有两个语句,即 console.log("句末")
和 console.log("isboyjc")
,那么我们说这个函数体内代码执行次数是 2
我们再来看函数 fn2
function fn2(n){
for(let i = 0; i < n; i++){
console.log("句末")
}
}
我们先来看函数 fn2
中有几条可执行语句
let i = 0
i < n
i++
console.log("句末")
我们假设 n = 3
,然后依次带入进去,看看各个执行语句执行了多少次
let i = 0
此条声明语句只在第一次 for
循环声明时执行 1 次
i < n
此条语句执行次数根据形参 n 的大小变化,n = 3
时,即 i=0,i=1,i=2,i=3
时会执行,即此条语句执行次数为 n + 1
次
i++
此条语句执行次数也是根据形参 n 的大小变化,n = 3
时,即 i=0,i=1,i=2
时会执行,即 n 次
console.log("句末")
此条语句执行次数还是根据形参 n 的大小变化,n = 3
会执行 3 次,那么此语句在函数内部即会执行 n 次
1 + (n + 1) + n + n = (3n + 2)
那么函数 fn2
内共执行 3n + 2
次
一段代码的总执行次数我们通常会用 T(n)
来表示,那么调用一次上面 fn1/fn2
两函数的总执行次数即
T(n) = 2 // fn1
T(n) = 3n + 2 // fn2
上面的 n,指的是为问题的规模,即输入数据的大小或者说数量,你也可以简单的理解为 T
就是函数本身,n
就是参数,也就是说
函数 fn1
任何情况下执行次数都为 2
函数 fn2
的总执行次数会根据 n 的大小变化而产生一个变化
我们思考一下,我们可以使用一段代码的执行总次数来衡量执行速度吗?
答案当然是不行的,当代码行数比较多的时候,我们一条一条的数来计算执行总次数太麻烦了,例如函数中套用函数时、循环中套用循环时,想要精确计算执行次数都是非常麻烦的
所以,在算法中,我们一般用 T(n)
简化后的估算值来表达代码执行的速度,通常我们用大些字母 O
来表示,即大 O
表示法,由于是估算,它表示的是代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度(asymptotic time complexity),简称时间复杂度
明确了这个概念以后,我们就可以来算时间复杂度了,还是用上面 fn1/fn2
两函数例
// fn1
T(n) = 2
// fn2
T(n) = 3n + 2
首先我们来看函数 fn1
,它的执行总次数为 2,一个 常数(常数级)
,也就是说此函数无论何时它的总执行次数都是 2,是一个不变的值,那么我们使用时间复杂度 O
来表示时直接估算为 1 就可以,即时间复杂度为 O(1)
我们再来看函数 fn2
,它的执行次数 T(n)
是 3n + 2
即 常数*n + 常数
,这里我们完全可以看成 常数*n
和 +常数
两部分,随着 n 的增大,只有前一个部分会有变化,后面是不变的,所以在表示时间复杂度时就可以忽略后面不变的常数,即 常数*n
,而 常数*n
中过的常数我们也可以直接当做 1,或者说忽略掉这个作为系数的常数,那么最终可以得出函数 fn2
的时间复杂度为 n,即 O(n)
「PS:晓得可能有人把数学知识还给老师了,所以解释下」
「常数:」 常数就是指平常的数值,可简单的理解为固定不变的数值
**系数:**系数指代数式的单项式中的数字因数,例如 a = 1 * a
,则它的系数为 1,2b = 2 * b
,则它的系数为 2
我们再来举个例子,如下面这个多项式代指一个函数的 T(n)
,求它的时间复杂度
T(n) = 10n^4 + 100n^2 + 1000
其实,对于多项式,我们只需要保留最高次项就行了,也就说,保留 n 的最高次方数就可以了,这个例子中保留的也就是 n 的 4 次方,系数和常数皆可以忽略,最终得到的时间复杂度即为 O(n^4)
「结论:」
T(n)
为常数时,时间复杂度为 O(1)
,反之时间复杂度为 O(保留T(n)的最高次项并去掉最高次项的系数)
接下来,我们看几个例子来判断下几段代码的时间复杂度
「例1:」
function fn01(){
console.log("你看这是啥")
console.log("这是一个输出")
console.log("哈哈哈")
let a = 1
return a
}
上面这个函数 fn01
中只有一条条的语句,共执行 5 次,毫无变化,时间复杂度即 O(1)
,此为常数级时间复杂度
「例2:」
function fn02(n){
for(let i = 0; i < n; i++){
console.log("这是一个输出以上是关于「算法与数据结构」时间与空间复杂度的主要内容,如果未能解决你的问题,请参考以下文章