阅读 231

树结构、扁平化数组相互转换(数组转树形结构)

前段时间看到了一篇文章,面试了十几个高级前端,竟然连(扁平数据结构转Tree)都写不出来,提到了根据数组转化为树结构,这种需求确实在开发中也有遇到,尤其是一些树图一类的处理。于是思考并整理了几个相关的算法,包括常见的递归和效率高一点的循环。

测试数据

const arr = [   {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}, ] const obj = {   id: 1,   name: "部门1",   pid: 0,   children: [     {       id: 2,       name: "部门2",       pid: 1,       children: []     },     {       id: 3,       name: "部门3",       pid: 1,       children: [         {           id: 4,           name: "部门4",           pid: 3,           children: [             {               id: 5,               name: "部门5",               pid:4,               children: []             }           ]         }       ]     }   ] } 复制代码

tips:以下算法均以上面的数据结构实现,主要体现思路,注意注释

array to tree 【生成树结构】

递归

定义方法获取指定 id 的所有 children,每个 children 递归调用该方法获取 children.

/**  * @param {arr: array 原数组数组, id: number 父节点id}   * @return {children: array 子数组}  */ function getChildren(arr, id) {   const res = [];   for (let item of arr) {     if (item.pid === id) { // 找到当前id的子元素       // 插入子元素,每个子元素的children通过回调生成       res.push({...item, children: getChildren(arr, item.id)});     }   }   return res; } getChildren(arr, 0); 复制代码

定义方法获取指定 idnodenode.children 的每个元素 调用该方法获取新的子元素

/**  * @param {arr: array 原数组数组, id: number 当前元素id}   * @return {children: array 子数组}  */ function getNode(arr, id){   const node = arr.find(i => i.id === id); // 找到当前元素   node.children = [];   for(const item of arr) {     if (item.pid === id) {       // 给当前元素添加子节点,每个子节点递归调用       node.children.push(getNode(arr, item.id));     }   }   return node; } getChildren(arr, 1); 复制代码

循环

利用 map 存储数组数据,并且每次循环处理对应的元素,利用指针指向内存空间的原理

/**  * @param {arr: array, pid: number}  * @return {obj: object}  */ function fn(arr, pid) {   const map = new Map(); // 生成map存储元素   for(const item of arr) {     if (!map.has(item.id)) { // 若map中没有当前元素,添加并初始化children       map.set(item.id, {...item, children: []});     }     if (map.has(item.pid)){ // 查找父元素,存在则将该元素插入到children       map.get(item.pid).children.push(map.get(item.id));     } else { // 否则初始化父元素,并插入children       map.set(item.pid, {children: [map.get(item.id)]});     }   }   return map.get(pid); } fn(arr, 0); 复制代码

tree to array 【扁平化数据】

递归

/**  * @param {obj: object, res: array}  * @return {arr: array}  */ function fn(obj, res = []) { // 默认初始结果数组为[]   res.push(obj); // 当前元素入栈   // 若元素包含children,则遍历children并递归调用使每一个子元素入栈   if (obj.children && obj.children.length) {     for(const item of obj.children) {       fn(item, res);     }   }   return res; } fn(arr); 复制代码

/**  * @param { arr: array } 接收子数组,调用时可以构造 `{children: [...]}` 开始调用  * @return { arr: array }  */ function fn(arr) {   const res = [];   res.push(...arr); // chilren插入结果数组   for(const item of arr) { // 遍历子元素,若包含children则递归调用     if (item.children && item.children.length) {       res.push(...fn(item.children))     }   }   return res; } fn([children: [obj]]); 复制代码

循环

基本大部分递归都可以用栈的思想改为循环,将每次需要处理的元素压入栈中,处理后弹出,至栈为空

/**  * @param {obj: object}   * @return {arr: array}  */ function fn(obj) {   const stack = [];         // 声明栈,用来存储待处理元素   const res = [];           // 接收结果   stack.push(obj);          // 将初始元素压入栈   while(stack.length) {     // 栈不为空则循环执行     const item = stack[0];  // 取出栈顶元素     res.push(item);         // 元素本身压入结果数组     stack.shift();          // 将当前元素弹出栈     // 逻辑处理,如果当前元素包含子元素,则将子元素压入栈     if (item.children && item.children.length) {       stack.push(...item.children);     }   }   return res; } fn(obj);


作者:张吉成
链接:https://juejin.cn/post/7037078362417791007

伪原创工具 SEO网站优化  https://www.237it.com/ 


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐