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

  1. let arr = [
  2. {id: 1, name: '部门1', pid: 0},
  3. {id: 2, name: '部门2', pid: 1},
  4. {id: 3, name: '部门3', pid: 1},
  5. {id: 4, name: '部门4', pid: 3},
  6. {id: 5, name: '部门5', pid: 4},
  7. ]

输出结果:

  1. [
  2. {
  3. "id": 1,
  4. "name": "部门1",
  5. "pid": 0,
  6. "children": [
  7. {
  8. "id": 2,
  9. "name": "部门2",
  10. "pid": 1,
  11. "children": []
  12. },
  13. {
  14. "id": 3,
  15. "name": "部门3",
  16. "pid": 1,
  17. "children": [
  18. // 结果 ,,,
  19. ]
  20. }
  21. ]
  22. }
  23. ]

什么是好算法,什么是坏算法

判断一个算法的好坏,一般从执行时间和占用空间来看,执行时间越短,占用的内存空间越小,那么它就是好的算法。对应的,我们常常用时间复杂度代表执行时间,空间复杂度代表占用的内存空间。

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


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

  1. /**
  2. * 递归查找,获取children
  3. */
  4. const getChildren = (data, result, pid) => {
  5. for (const item of data) {
  6. if (item.pid === pid) {
  7. const newItem = {...item, children: []};
  8. result.push(newItem);
  9. getChildren(data, newItem.children, item.id);
  10. }
  11. }
  12. }
  13. /**
  14. * 转换方法
  15. */
  16. const arrayToTree = (data, pid) => {
  17. const result = [];
  18. getChildren(data, result, pid)
  19. return result;
  20. }

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

不用递归,也能搞定

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

  1. function arrayToTree(items) {
  2. const result = []; // 存放结果集
  3. const itemMap = {}; //
  4. // 先转成map存储
  5. for (const item of items) {
  6. itemMap[item.id] = {...item, children: []}
  7. }
  8. for (const item of items) {
  9. const id = item.id;
  10. const pid = item.pid;
  11. const treeItem = itemMap[id];
  12. if (pid === 0) {
  13. result.push(treeItem);
  14. } else {
  15. if (!itemMap[pid]) {
  16. itemMap[pid] = {
  17. children: [],
  18. }
  19. }
  20. itemMap[pid].children.push(treeItem)
  21. }
  22. }
  23. return result;
  24. }

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

最优性能

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

  1. function arrayToTree(items) {
  2. const result = []; // 存放结果集
  3. const itemMap = {}; //
  4. for (const item of items) {
  5. const id = item.id;
  6. const pid = item.pid;
  7. if (!itemMap[id]) {
  8. itemMap[id] = {
  9. children: [],
  10. }
  11. }
  12. itemMap[id] = {
  13. ...item,
  14. children: itemMap[id]['children']
  15. }
  16. const treeItem = itemMap[id];
  17. if (pid === 0) {
  18. result.push(treeItem);
  19. } else {
  20. if (!itemMap[pid]) {
  21. itemMap[pid] = {
  22. children: [],
  23. }
  24. }
  25. itemMap[pid].children.push(treeItem)
  26. }
  27. }
  28. return result;
  29. }

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

从我们的测试结果来看,随着数量的增大,递归的实现会越来越慢,基本成指数的增长方式。