扁平数据结构转Tree树形结构

有一套考察算法的小题目。后台返回一个扁平的数据结构,转成树。

我们看下题目:打平的数据内容如下:

let 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},
]

输出结果

[
    {
        "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(log2 n)
  • 线性阶O(n)
  • 线性对数阶O(n log2 n)
  • 平方阶O(n^2)
  • 立方阶O(n^3)
  • k次方阶O(n^K)
  • 指数阶O(2^n)

计算方法

  1. 选取相对增长最高的项
  2. 最高项系数是都化为1
  3. 若是常数的话用O(1)表示

举个例子:如f(n)=3*n^4+3n+300 则 O(n)=n^4

通常我们计算时间复杂度都是计算最坏情况。计算时间复杂度的要注意的几个点

  • 如果算法的执行时间不随n增加增长,假如算法中有上千条语句,执行时间也不过是一个较大的常数。 此类算法的时间复杂度是O(1)。 举例如下: 代码执行100次,是一个常数,复杂度也是O(1)
    let x = 1;
    while (x <100) {
     x++;
    }
  • 多个循环语句时候,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的方法决定的。 举例如下: 在下面for循环当中,外层循环每执行一次内层循环要执行n次,执行次数是根据n所决定的,时间复杂度是O(n^2)
  for (i = 0; i < n; i++){
         for (j = 0; j < n; j++) {
             // ...code
         }
     }
  • 循环不仅与n有关,还与执行循环判断条件有关。 举例如下: 在代码中,如果arr[i]不等于1的话,时间复杂度是O(n)。 如果arr[i]等于1的话,循环不执行,时间复杂度是O(0)
    for(var i = 0; i<n && arr[i] !=1; i++) {
    // ...code
    }

空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间的大小。

计算方法:

  1. 忽略常数,用O(1)表示
  2. 递归算法的空间复杂度=(递归深度n)*(每次递归所要的辅助空间)

计算空间复杂度的简单几点

  • 仅仅只复制单个变量,空间复杂度为O(1)。 举例如下: 空间复杂度为O(n) = O(1)。
   let a = 1;
   let b = 2;
   let c = 3;
   console.log('输出a,b,c', a, b, c);
  • 递归实现,调用fun函数,每次都创建1个变量k。 调用n次,空间复杂度O(n*1) = O(n)。
    function fun(n) {
       let k = 10;
       if (n == k) {
           return n;
       } else {
           return fun(++n)
       }
    }

不考虑性能实现,递归遍历查找

主要思路是提供一个递getChildren的方法,该方法递归去查找子集。就这样,不用考虑性能,无脑去查,大多数人只知道递归,就是写不出来。。。

/**
 * 递归查找,获取children
 */
const getChildren = (data, result, pid) => {
  for (const item of data) {
    if (item.pid === pid) {
      const newItem = {...item, children: []};
      result.push(newItem);
      getChildren(data, newItem.children, item.id);
    }
  }
}

/**
* 转换方法
*/
const arrayToTree = (data, pid) => {
  const result = [];
  getChildren(data, result, pid)
  return result;
}

从上面的代码我们分析,该实现的时间复杂度为O(2^n)

不用递归,也能搞定

主要思路是先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储

function arrayToTree(items) {
  const result = [];   // 存放结果集
  const itemMap = {};  // 
    
  // 先转成map存储
  for (const item of items) {
    itemMap[item.id] = {...item, children: []}
  }
  
  for (const item of items) {
    const id = item.id;
    const pid = item.pid;
    const treeItem =  itemMap[id];
    if (pid === 0) {
      result.push(treeItem);
    } else {
      if (!itemMap[pid]) {
        itemMap[pid] = {
          children: [],
        }
      }
      itemMap[pid].children.push(treeItem)
    }

  }
  return result;
}

从上面的代码我们分析,有两次循环,该实现的时间复杂度为O(2n),需要一个Map把数据存储起来,空间复杂度O(n)

最优性能

主要思路也是先把数据转成Map去存储,之后遍历的同时借助对象的引用,直接从Map找对应的数据做存储。不同点在遍历的时候即做Map存储,有找对应关系。性能会更好。

function arrayToTree(items) {
  const result = [];   // 存放结果集
  const itemMap = {};  // 
  for (const item of items) {
    const id = item.id;
    const pid = item.pid;

    if (!itemMap[id]) {
      itemMap[id] = {
        children: [],
      }
    }

    itemMap[id] = {
      ...item,
      children: itemMap[id]['children']
    }

    const treeItem =  itemMap[id];

    if (pid === 0) {
      result.push(treeItem);
    } else {
      if (!itemMap[pid]) {
        itemMap[pid] = {
          children: [],
        }
      }
      itemMap[pid].children.push(treeItem)
    }

  }
  return result;
}

从上面的代码我们分析,一次循环就搞定了,该实现的时间复杂度为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://cloud.tencent.com/developer/article/2110952

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340