前言 招聘季节一般都在金三银四,或者金九银十。最近在这五六月份,陆陆续续面试了十几个高级前端。有一套考察算法的小题目。后台返回一个扁平的数据结构,转成树。 我们看下题目:打平的数据内容如下:letarr〔{id:1,name:部门1,pid:0},{id:2,name:部门2,pid:1},{id:3,name:部门3,pid:1},{id:4,name:部门4,pid:3},{id:5,name:部门5,pid:4},〕复制代码 输出结果〔{id:1,name:部门1,pid:0,children:〔{id:2,name:部门2,pid:1,children:〔〕},{id:3,name:部门3,pid:1,children:〔结果,,,〕}〕}〕复制代码 我们的要求很简单,可以先不用考虑性能问题。实现功能即可,回头分析了面试的情况,结果使我大吃一惊。 10的人没思路,没碰到过这种结构 60的人说用过递归,有思路,给他个笔记本,但就是写不出来 20的人在引导下,磕磕绊绊能写出来 剩下10的人能写出来,但性能不是最佳 感觉不是在招聘季节遇到一个合适的人真的很难。 接下来,我们用几种方法来实现这个小算法什么是好算法,什么是坏算法 判断一个算法的好坏,一般从执行时间和占用空间来看,执行时间越短,占用的内存空间越小,那么它就是好的算法。对应的,我们常常用时间复杂度代表执行时间,空间复杂度代表占用的内存空间。时间复杂度 时间复杂度的计算并不是计算程序具体运行的时间,而是算法执行语句的次数。 随着n的不断增大,时间复杂度不断增大,算法花费时间越多。常见的时间复杂度有常数阶O(1)对数阶O(log2n)线性阶O(n)线性对数阶O(nlog2n)平方阶O(n2)立方阶O(n3)k次方阶O(nK)指数阶O(2n)计算方法选取相对增长最高的项最高项系数是都化为1若是常数的话用O(1)表示 举个例子:如f(n)3n43n300则O(n)n4 通常我们计算时间复杂度都是计算最坏情况。计算时间复杂度的要注意的几个点如果算法的执行时间不随n的增加而增长,假如算法中有上千条语句,执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。举例如下:代码执行100次,是一个常数,复杂度也是O(1)。letx1;while(x100){x;}复制代码有多个循环语句时候,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的方法决定的。举例如下:在下面for循环当中,外层循环每执行一次,内层循环要执行n次,执行次数是根据n所决定的,时间复杂度是O(n2)。for(i0;in;i){for(j0;jn;j){。。。code}}复制代码循环不仅与n有关,还与执行循环判断条件有关。举例如下:在代码中,如果arr〔i〕不等于1的话,时间复杂度是O(n)。如果arr〔i〕等于1的话,循环不执行,时间复杂度是O(0)。for(vari0;inarr〔i〕!1;i){。。。code}复制代码空间复杂度 空间复杂度是对一个算法在运行过程中临时占用存储空间的大小。计算方法:忽略常数,用O(1)表示递归算法的空间复杂度(递归深度n)(每次递归所要的辅助空间) 计算空间复杂度的简单几点仅仅只复制单个变量,空间复杂度为O(1)。举例如下:空间复杂度为O(n)O(1)。leta1;letb2;letc3;console。log(输出a,b,c,a,b,c);复制代码递归实现,调用fun函数,每次都创建1个变量k。调用n次,空间复杂度O(n1)O(n)。functionfun(n){letk10;if(nk){returnn;}else{returnfun(n)}}复制代码不考虑性能实现,递归遍历查找 主要思路是提供一个递getChildren的方法,该方法递归去查找子集。就这样,不用考虑性能,无脑去查,大多数人只知道递归,就是写不出来递归查找,获取childrenconstgetChildren(data,result,pid){for(constitemofdata){if(item。pidpid){constnewItem{。。。item,children:〔〕};result。push(newItem);getChildren(data,newItem。children,item。id);}}}转换方法constarrayToTree(data,pid){constresult〔〕;getChildren(data,result,pid)returnresult;}复制代码 从上面的代码我们分析,该实现的时间复杂度为O(2n)。不用递归,也能搞定 主要思路是先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储functionarrayToTree(items){constresult〔〕;存放结果集constitemMap{};先转成map存储for(constitemofitems){itemMap〔item。id〕{。。。item,children:〔〕}}for(constitemofitems){constiditem。id;constpiditem。pid;consttreeItemitemMap〔id〕;if(pid0){result。push(treeItem);}else{if(!itemMap〔pid〕){itemMap〔pid〕{children:〔〕,}}itemMap〔pid〕。children。push(treeItem)}}returnresult;}复制代码 从上面的代码我们分析,有两次循环,该实现的时间复杂度为O(2n),需要一个Map把数据存储起来,空间复杂度O(n)最优性能 主要思路也是先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储。不同点在遍历的时候即做Map存储,有找对应关系。性能会更好。functionarrayToTree(items){constresult〔〕;存放结果集constitemMap{};for(constitemofitems){constiditem。id;constpiditem。pid;if(!itemMap〔id〕){itemMap〔id〕{children:〔〕,}}itemMap〔id〕{。。。item,children:itemMap〔id〕〔children〕}consttreeItemitemMap〔id〕;if(pid0){result。push(treeItem);}else{if(!itemMap〔pid〕){itemMap〔pid〕{children:〔〕,}}itemMap〔pid〕。children。push(treeItem)}}returnresult;}复制代码 从上面的代码我们分析,一次循环就搞定了,该实现的时间复杂度为O(n),需要一个Map把数据存储起来,空间复杂度O(n)小试牛刀 方法 1000(条) 10000(条) 20000(条) 50000(条) 递归实现 154。596ms 1。678s 7。152s 75。412s 不用递归,两次遍历 0。793ms 16。499ms 45。581ms 97。373ms 不用递归,一次遍历 0。639ms 6。397ms 25。436ms 44。719ms 从我们的测试结果来看,随着数量的增大,递归的实现会越来越慢,基本成指数的增长方式。结束语 大家觉得高级前端,该不该很顺利的把这个给写出来。评论区留下你的见解。有比以上更好的实现,评论区留下你的答案,大家一起学习。 原文地址:https:juejin。cnpost6983904373508145189