JS 记一次工作中,由深度优先到广度优先的算法优化

壹 ❀ 引
坦白的说,本人的算法简直一塌糊涂,虽然有刷过一段时间的算法题,但依然只能解决不算复杂的问题,稍微麻烦的问题都只是站在能不能解决问题的角度,至于性能优化,算法方法的选择并没有过于深刻的理解。较巧的是,最近在工作中正好遇到了一个实际场景,整体修改下来也算感受颇深,便记录于此,做个小分享,那么本文开始。
贰 ❀ 乞丐版深度优先
需求其实很简单,比如企业微信中有人员组织架构,类似如下:

简单来理解就是,一个公司的员工会被划分到多个部门,比如一级部门A,B,C...,一级部门下也可能还有二级部门如A-1-1,同理二级部门A-1-1下可能还有三级部门。有些员工可能角色特殊,会同时属于多个不同层级的部门,现在需求就是,后端会返回一个该员工所属部门的ID数组,你需要在上述部门数据中利用ID数组查找出对应的部门名字(name),同时要按一级=>二级=>三级类似的顺序排列并组合成字符串作为展示。
如下提供了模拟数据,因为2对应的是UI,层级比5的前端二组高,所以期望得到的结果为UI;前端二组,那么怎么做呢?
// 某用户所在的部门id
const departmentsId = [5,2];
// 部门模拟数据
const departmentTree = [
{
name: "技术部",
id: 0,
children: [
{
id: 1,
name: "前端",
children: [
{
id: 4,
name: "前端一组",
children: [],
},
{
id: 5,
name: "前端二组",
children: [
{
id: 9,
name: "前端-移动端一组",
children: [],
},
],
},
],
},
{
id: 2,
name: "UI",
children: [
{
id: 6,
name: "UI一组",
children: [{ id: 10, name: "视觉设计", children: [] }],
},
],
},
{
id: 3,
name: "后端",
children: [
{
id: 7,
name: "后端一组",
children: [],
},
{
id: 8,
name: "后端二组",
children: [],
},
],
},
],
},
];
我首先想到的就是遍历departmentIds,每次拿一个部门ID到departmentTree中查找,由于层级高的要排在前面,所以想到深度遍历,每往下一层都会记录当前的depth属性,如果找到了,最终会到如一个包含部门name和此部门depth的对象,因为是深度优先,所以需要结合递归,我的实现是这样:
const getDeptNameFromTreeDepts = (treeDepartments, departmentid) => {
let departmentNameInfo = "";
function departmentTraversal(node, depth) {
// 这里利用find,找到了就没必要继续后续查找了
const targetDepartment = node.find((department) => {
// 判断当前部门id是否和提供的id相同
return department.id === departmentid
? true
: department.children && //如果不相同,判断有没有children,同时让depth加1
department.children.length &&
departmentTraversal(department.children, depth + 1);
});
// 记录当前部门name和depth属性
if (targetDepartment) {
departmentNameInfo = {
name: targetDepartment.name,
depth: depth + 1,
};
}
}
treeDepartments.forEach(({ children }) => {
departmentTraversal(children, 0);
});
return departmentNameInfo;
};
//用于记录每次查找到的结果
const departmentNames = [];
// 遍历人员所属部门id,一次查找一个。
departmentIds.forEach((departmentId) => {
const transformedDepartment = getDeptNameFromTreeDepts(
departmentTree,
departmentId
);
if (transformedDepartment) {
departmentNames.push(transformedDepartment);
}
});
// 根据depth属性来决定部门先后并做name拼接
const name = departmentNames
.sort((a, b) => {
return a.depth - b.depth;
})
.reduce((acc, cur) => {
return `${acc + cur.name}; `;
}, "");
console.log(name);// UI; 前端二组;
代码看着有点多,确实比较复杂,抛开递归不说,最终得到了目标数组,还需要根据depth属性对部门name排序,之后再做字符拼接,而且得到的结果是UI; 前端二组; ,我们想要的是UI; 前端二组,尾部还多了一个;,理论上来说还要做一次额外处理。在发版前提测测试通过没问题,很遗憾,在code review环节未通过(我算法烂,其实心里也感觉通过不了),得到的反馈如下:
- 效率太低,一次只能查询一个部门id,应该支持批量查询
- 遍历次数过多,最后为什么不用
join直接拼接name。 - 不应该是深度优先,改为广度优先
叁 ❀ 广度优先优化
我看到反馈其实心里也有疑虑,就找到了review的前辈,说之所以没用join是因为最终得到的数组并不是包含纯部门的name,而是多个对象数组,它们之前并无先后顺序,所以需要根据depth来做排序最后做拼接。
前辈说不应该啊,你在查找的时候不是已经知道了多个目标部门的先后顺序了吗,为什么还要利用depth呢?我当时还没反应过来,问他难道在查找的时候顺便做一次插入排序?这样就能保证返回的结果已经带有顺序。他说广度优先不是从上到下一层一层的找吗?你直接把Tree遍历一次,每到了一个节点,看这个节点在不在departmentsId里面,如果在,那说明是你想要的,由于广度优先是一层层向下,所以你查找出来的结果已经自带层级排序了,写算法之前一定要先设计好自己的算法思路,这样才能少走弯路。我顿时恍然大悟!!!立马回去改了代码!!!
一番修改,于是得到了广度优先的实现代码:
const getDeptNameFromTreeDepts = (treeDepartments, departmentIds) => {
const departmentNames = [];
// 浅拷贝一份,不然在做队列操作时会影响原数据
const queue = [...treeDepartments];
while (queue.length > 0) {
// 每次从对了头部取一个用于做对比
const department = queue.shift();
// 判断当前节点的部门uuid在不在departmentUuid数组中,在的话就是我们想要找的部门,并提取name属性
if (departmentIds.includes(department.id)) {
departmentNames.push(department.name);
}
// 将当前部门的部门的子部门加到队列尾部
department.children && queue.push(...department.children);
}
return departmentNames.length > 0 ? departmentNames.join("; ") : "";
};
const name = getDeptNameFromTreeDepts(departmentTree, departmentIds);
console.log(name);// UI; 前端二组
所以实现到这,我整个人都傻了,代码量直接少了一大半不说,得到的结果也不需要做额外处理。其次,我们在前面的实现中,虽然可以保证不同层级的部门排序,但却无法保证同级部门的先后排序。比如第一层级部门顺序为前端、UI、后端,在前面的实现中,我们是依次拿departmentsId中的id去查找,所以同级的先后顺序其实被departmentsId中id的先后顺序给决定了,在前的会被先找到,例如前面的实现,假设departmentsId为[3,2],查询出来的结果就是后端;UI,与Tree的顺序并不一致。
而在后续的修改中,同级顺序的问题也不需要考虑了,因为我们一共就遍历了一次Tree,同级节点如果满足,自然会被先找到,所以这段修改不仅解决了不同层级的先后排序,同时也满足了同级部门顺序与Tree一致,大功告成。
肆 ❀ 总
虽然这并不是一次很难的需求,但是在改完代码之后,老实说我心里感触很大。本人算法确实一塌糊涂,很多场景只是能达到实现的角度,缺少了对于算法选择的大局观。正如前辈所说,有时候的你的算法选择应该更去贴合需求,如果你期望结果是ABAB,很明显你应该用深度优先,但如果你要的是AABB,这时候广度优先会更明智,之所以你写了那么多非必要代码,就是因为在广度优先的场景下你用了深度优先。
我虽然之前了解广度深度的概念,但确实缺少实际场景的经验,就像你学了知识,该用到的时候你完全就没有这个概念。所以这一次也算是自己对于广度深度的一次不错的理解了,那么本文结束。
JS 记一次工作中,由深度优先到广度优先的算法优化的更多相关文章
- 记一次工作中的小BUG
今天在调试代码的时候总是遇到一个bug,百思不得其解!先上bug图 我用的webapi 集成的swagger,错误提示是路由名称冲突,可我仔细检查了下并没有冲突的路由地址啊!于是上网查找资料,有位网友 ...
- 工作中遇到的99%SQL优化,这里都能给你解决方案
前几篇文章介绍了mysql的底层数据结构和mysql优化的神器explain.后台有些朋友说小强只介绍概念,平时使用还是一脸懵,强烈要求小强来一篇实战sql优化,经过周末两天的整理和总结,sql优化实 ...
- 工作中常用的js、jquery自定义扩展函数代码片段
仅记录一些我工作中常用的自定义js函数. 1.获取URL请求参数 //根据URL获取Id function GetQueryString(name) { var reg = new RegExp(&q ...
- 原生js(form)验证,可以借鉴下思路,应用到工作中
我在工作中时常使用form验证,在目前的公司做的表单验证用的angular的form组件,对于一个有追求的前端,或者应用在移动端写个form验证,引入angular或者jquery组件等验证,难免显得 ...
- JS单例模式在工作中的使用
为了尽可能的减少全局变量的污染,在写js的时候可以采用单例模式,形式如下: 比如有一个js叫demo.js,那么我们可以在js里这样写: var demo = {} 这样做的目的是将整个js当成一个对 ...
- 记录下工作中使用的pdf.js
在工作中遇到一个通过网页的形式浏览pdf文件以及图片的需求,图片简单,直接通过网页的形式打开这个图片的URL即可.而pdf这边,通过查询发现有一个名为pdf.js的神器. 简单介绍下,它可以在html ...
- 干货|工作中要使用Git,看这篇文章就够了
本文将从 Git 入门到进阶.由浅入深,从常用命令.分支管理.提交规范.vim 基本操作.进阶命令.冲突预防.冲突处理等多方面展开,足以轻松应对工作中遇到的各种疑难杂症,如果觉得有所帮助,还望看官高抬 ...
- Sticker.js – 帮助你在网站中加入贴纸效果
Sticker.js 是一个很小的 JavaScript 库,它允许您在网页中创建漂亮的贴纸效果.没有依赖关系(不需要 jQuery),可以在大多数支持 CSS3 的主流浏览器工作.下面有简单的使用示 ...
- [工作中的设计模式]原型模式prototype
一.模式解析 提起prototype,最近看多了js相关的内容,第一印象首先是js的原型 var Person=function(name){ this.name=name; } Person.pro ...
- 工作中使用seajs后的一些总结
工作中用seajs一段时间了,小小地总结一下. 使用seajs五部曲: 1.布置你项目的目录结构 2.设置seajs的config项,我一般是单独一个js文件--> seajs-config.j ...
随机推荐
- python常见面试题讲解(九)字符个数统计
题目描述 编写一个函数,计算字符串中含有的不同字符的个数.字符在ACSII码范围内(0~127),换行表示结束符,不算在字符里.不在范围内的不作统计.注意是不同的字符 输入描述: 输入N个字符,字符在 ...
- Windows 下 Outlook 点击关闭最小化和开机自动运行
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...
- 百度网盘(百度云)SVIP超级会员共享账号每日更新(2023.12.18)
一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...
- [转帖]TLS 加速技术:Intel QuickAssist Technology(QAT)解决方案
https://zhuanlan.zhihu.com/p/631184323 3 人赞同了该文章 作者:vivo 互联网服务器团队- Ye Feng 本文介绍了 Intel QAT 技术方案,通过 ...
- [转帖]tidb-系统内核调优及对比
一.背景 验证系统调优对性能的影响,用sysbench做了一些简单的测试,具体调整方法可见官方文档 二.特殊说明 1.透明大页查看 # 查看透明大页是否开启,[]在always处表示开启,[]在nev ...
- [转帖]jumpserver (Linux资产管理快速入门)
准备工作 准备三台虚拟机,一台作为jumpserver的服务端,两台作为测试端. 一.安装好jump server后,输入IP地址登录 [192.168.2.111为本机测试地址] 二.创建用户组 这 ...
- [转帖]Dapper,大规模分布式系统的跟踪系统
http://bigbully.github.io/Dapper-translation/ 作者:Benjamin H. Sigelman, Luiz Andr´e Barroso, Mike Bur ...
- 关于SSL证书的学习与总结
关于证书 证书是用来实现https通信加密的基础, 有证书才能够进行相关的TLS层的加密处理. 本文简要讲解一下证书的申请,创建以及使用等. 第一部分: PKI 公共密钥基础 其实有很多家企业在做PK ...
- SAP PO7.5 有关https 接口body编码格式 application/x-www-form-urlencoded
近期项目中,在PO中做接口 遇到OAUTH2.0认证方式,token获取过程中编码格式为 "application/x-www-form-urlencoded" 实现过程错误记录: ...
- Seata配置参考
注意:mysql.redis等连接密码需修改为相应值 Seata-Server 环境 版本:1.4.2 OS: CentOS Linux release 7.5.1804 (Core) ip:192. ...