JS 数据结构与算法教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!
数据结构是一种具备一定的逻辑结构的数据元素集合,这个结构指的是数据元素之间的关系,分为逻辑结构和存储结构。
算法是用来解决问题的,有简单的,比如找出数组中的最大值;也有复杂的,比如找出起始点到目标点所有路径中的最短路径。
算法的设计取决于数据的逻辑结构,而算法的实现取决于数据的存储结构。逻辑结构是指数据之间的逻辑关系,就是我们上面所说的一对一、一对多等。存储结构是指我们这个数据在计算机中的存储形式,比如顺序存储、链式存储、索引存储、哈希存储等。
数据结构与算法是每一名计算机工程师必备的技能,本系列采用JavaScript编程语言,现在就让我们开始学习数据结构与算法吧!
怎么衡量算法的好坏
算法是变化莫测的,每一个问题都有很多种解决方案。
比如计算 1-100 的和。第一种,我们一个一个累加计算1+2+3+...;第二种,我们采用等差数列求和方式,(1+100)+(2+99)+(3+98)+...。那么上面两种,哪一个是更加高效的呢?显而易见,第二种更加高效,虽然第一种最终也可以得到正确答案,但是其计算过程要低效的多。
在计算机中,我们衡量一个算法的好坏,有两个重要标准:
时间复杂度
空间复杂度
顾名思义,时间复杂度指的是我们的算法的执行时间;空间复杂度指的是我们的算法所占用的内存空间大小。
但是,当代码还没有运行的时候,我们不可能知道我们的代码所执行的时间和空间的。这个时候,我们就需要根据输入规模的大小,来预估算法的基本操作执行次数。
渐进时间复杂度
渐进时间复杂度就是我们处理一个问题时,所用时间的一个方程组,记作T(n)。
比如,你在吃瓜子,每秒钟你可以吃一粒瓜子,那么10个瓜子你需要多长时间?10秒钟。 那么如果n个瓜子呢?就是n秒钟。记作,T(n) = n。
再比如,你还在吃瓜子,你每分钟吃一半,那么把瓜子吃的只剩下一粒,你需要多长时间?这里我们分解一下,第一次:吃8个,第二次:吃4个;第三次:吃两个;第四次;吃一个;此时,剩下的瓜子就只有一个了。我们就是16不断的除以2,记作log16。那么如果n个瓜子呢?就是logn。记作,T(n) =logn。
第三个例子,你还在吃瓜子,你吃第一个瓜子需要1分钟,吃第二个瓜子要两分钟,吃第三个瓜子要三分钟...,那么10个瓜子你需要多长时间呢?答案是1+2+3+4+...+10 = 55分钟。那么如果n个瓜子呢?就是1+2+3+...+n = n*(1+n)/2 = 0.5n^2 + 0.5n。记作,T(n) = 0.5n^2 + 0.5n。
若存在函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称为O(f(n)),它随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称做算法的渐进时间复杂度,渐进就是算法规模一点点变大的过程,简称为时间复杂度。
上面三个例子的时间复杂度,分别是 O(n)、O(logn)、O(n^2)。第三个例子中,0.5n^2 + 0.5n,0.5n^2相比0.5n来说,所占的权重更大,所以可以省略0.5n,而 0.5n^2,我们也可以省略系数 0.5,所以最终结果为 O(n^2)。只保留T(n)中的最高阶项,如果最高阶项存在,则省去最高阶项前面的系数。这里涉及到高等数学的求极限概念。
计算时间复杂度
我们先举个小例子:
// 例 1
{ ++a }
// 例 2
for(let i = 1;i<n;i++){
++a
}
// 例 3
for(let i = 1;i<n){
for(let j = 1;j<n){
++a
}
}
我们来看上面代码中,"++a" 执行的次数。例1中,执行了 1 次;例 2 中,执行了 n 次;例 3 中,执行了 n^2 次。相对应的,例1中的算法的时间复杂度就是O(1),例 2 是 O(n),例3是 O(n^2)。
计算算法的时间复杂度的时候,一般情况下,我们只需要找到一种基本操作来讨论算法的时间复杂度即可,这里我们找到的是“a增加1”的次数。对于复杂的算法而言,也可以找几种基本操作,来反映执行不同操作所需的时间,让我们的算法衡量更准确全面。
什么是空间复杂度
顾名思义,空间复杂度就是算法中保存的临时变量所占据的内存容量的大小。记作 S(n) = O(f(n)) 。
计算空间复杂度
空间复杂度也是随着问题的规模呈一定的变化趋势的,比如下面的几个例子。
// 例 1
function fn(n){
let arr = 1
}
// 例 2
function fn(n){
let arr = new Array(n)
}
// 例 3
function fn(n){
let arr = new Array(n)
arr.forEach(item=>{
item = new Array(n)
})
}
// 例 4
function fn(n){
if(n<=1) return
fn(n-1)
}
例 1 中,变量所占用的空间大小为规定值1,所以它的空间复杂度记作O(1);
例 2 中,变量所占用的空间大小与输入的规模成正比,记作O(n);
例 3 中,变量所占用的空间大小是一个二维空间,记作O(n^2);
例 4 中,算法是一个递归算法,当我们 fn() 函数递归调用的时候,会专门分配一块内存,来存储方法调用栈,这时候所占用的空间大小和我们的递归深度成正比,如果递归深度为n,空间复杂度则记作 O(n)。
JS 数据结构与算法教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的。若感本文对您有所帮助请点个赞吧!
叶阳辉
HFun 前端攻城狮