如何学习前端算法?
一、什么是算法
算法是解决问题方案的准确而且完整的描述,通过一系列明确的指令解决问题。不同的算法在解决相同的问题时,所消耗的时间、空间效率不同。所以我们用时间复杂度和空间复杂度来衡量一个算法的优劣。[1]
二、算法的特征[1]
有穷性:一个合法的算法必然能通过有限个步骤解决问题。
确切性:算法的每个步骤都有明确的意义。
输入项:一个算法必然有 0 个或多个输入项。
输出项:一个算法必然有 1 个或多个输出项,没有结果输出的算法毫无意义
可行性:算法在解决问题时,每一个步骤都可以在有限的时间内完成。
三、时间复杂度 & 空间复杂度
1. 时间复杂度
时间复杂度是一个函数,记为 O(n),它用来描述算法运行的时间。[2]
那么让我们来举个栗子:
现在我们有一块蛋糕,要分给4个小朋友,在不考虑每个小朋友分到蛋糕大小相等的情况,我们要如何切蛋糕呢?
首先我们可以十字刀切蛋糕,这样两刀我们就可以把蛋糕分好了。还有什么办法呢?因为我们不关心蛋糕的大小,所以我们也可以同方向平行的切三刀把蛋糕分成四块。
好了,那么我们可以发现,我们可以用两刀解决问题、也可以用三刀解决问题,这里用了几刀解决了问题就是我们的时间复杂度。
通常我们使用算法的最坏情况复杂度,记为 T(n) ,定义为任何大小的输入n所需要最大的运行时间。上面两个切蛋糕的方案,我们可以分别记为 T(n) = 2n \ T(n) = 3n。
接下来我们又遇到了新的问题了,有的小伙伴使用塑料餐刀切蛋糕用了五分钟才切好一刀,有的小伙伴使用40米大刀一秒切好一刀,他们都是用了两刀把蛋糕切好了,谁的速度更快呢?
这个问题就是在我们都是切了两刀,但是使用的切刀不同,也会导致我们分配蛋糕的时间变长。
所以我们使用了渐进时间复杂度:假设问题输入项有n个,当n趋于无穷大的时候,O(n)所得到的极限值。[3]
举个栗子,现在我们有两个算法,它们的时间复杂度分别为:
T(n) = 3n^2 + 2n + 1 和 T(n) = 100n^4 + 999
先看一个公式:T(n) = 3n^2 + 2n + 1。当n趋于无穷大的时候,最后的1可以忽略不计了。接下来看 3n^2 和 2n ,我们发现当n越大的时候,两者差距就越大,当n趋于无穷大的时候,3n^2 远远大于 2n,所以2n我们也可以忽略不计了。最后我们只剩下 3n^2 ,当n无穷大的时候,它再乘以3貌似对结果变得更大没有起到质的变化,所以我们省略掉3。最后我们剩下来最终的结果:T(n) = n^2
在看第二个公式:T(n) = 100n^4 + 999。当n趋于无穷大的时候,999已经无足轻重了,自动忽略。从上一个公式的经验,我们就会自然忽略 100,最终剩下 T(n) = n^4
最终我们对比两个公式的优劣的时候,自然可以看出来当n越来越大的时候,第二个公式会比第一个公式耗时更长,所以第一个公式更好。
那么如果出现了公式:T(n) = 3n^5 + 100n^3 + 10n^2,这种情况呢?按照我们第一个公式忽略的方案,当n趋于无穷大的时候,n^5要远远比n^3和n^2大,所以自然我们忽略掉 100n^3 和10n^2,只保留n^5,最终结果是 T(n) = n^5。
最终我们来总结一下时间复杂度:
它是一个描述算法运行时间的函数 O(n)
我们所讨论的时间复杂度常常使用的是最坏的情况 T(n)
推导一个算法的时间复杂度,首先忽略常数、只保留函数中的最高阶、省去最高阶项的系数[4]
2. 空间复杂度
空间复杂度是指在算法运行中时临时存储空间大小的量度,记为 S(n) = O(f(n))。[5] 简单来说就是,我们在代码执行中所占的内存大小。
那么如何去评定空间复杂度呢?方式和时间复杂度类似,我们也是根据输入项n来做判断。
举个栗子,现在我们要向一个数组插入一条新的数据,这个操作不会有额外的内存占有,所以它的空间复杂度是 S(n) = O(1)
再举个栗子,我们现在要使用递归去解决一个问题,那么就存在每次向下递归时,都需要临时存储一下我们的数据,所以当递归至最底层时,临时存储了n个数据,所以它的空间复杂度是 S(n) = O(n)
最终我们来总结一下空间复杂度:
它是指算法运行中所占用临时空间大小的量度
如果没有额外的临时空间占用,则空间复杂度为 O(1)
额外占用空间是以输入项n为比例计算的