面试官:扁平结构转树状结构常见写法有哪些?区别?

123 阅读3分钟

前言

无论你是 Java,Python,还是 PHP,面试总逃脱不了一个问题:算法!

据统计,各大厂笔试平均通过率只有 10%~20%,基本都折在了算法上。

很多同学都问过这个问题,毕竟,在实际工作中,我们几乎根本不可能从底层实现一遍经典算法。如果真的以工作内容为导向,算法还真可能对绝大部分同学来说没什么用。

但是,算法却是大厂面试考察的重点。甚至,极端一些,国外一些大厂只考算法。

为什么会这样?在这篇文章中,我打算系统阐述一下这个问题。相信会对你有启发。

递归方法

递归是解决树形结构问题的一种常用方法。对于扁平数据结构,我们可以通过递归的方式构建出一棵树。具体来说,我们可以从根节点开始,逐个遍历所有节点。对于每个节点,我们可以通过它的父节点ID来找到它的父节点,然后将其添加到父节点的子节点中。这样我们就可以递归地构建出一棵完整的树形结构。

function flatToTree(flatArr, rootId) {
  const tree = {};
  flatArr.forEach(node => {
    if (node.id === rootId) {
      tree[node.id] = node;
    } else {
      if (!tree[node.parentId].children) {
        tree[node.parentId].children = {};
      }
      tree[node.parentId].children[node.id] = node;
    }
  });
  return tree[rootId];
}

优点是代码简洁易懂,适合处理小规模数据。缺点是对于大规模数据的处理会导致栈溢出,性能较差

栈方法

除了递归,我们还可以使用栈来解决这个问题。我们可以先将根节点入栈,然后逐个遍历所有节点。对于每个节点,我们可以通过它的深度来判断它应该添加到哪个节点下面。具体来说,我们可以将栈中深度小于当前节点深度的节点全部弹出,然后将当前节点添加到栈顶节点的子节点中。这样我们就可以使用栈来构建出一棵完整的树形结构。

function flatToTree(flatArr, rootId) {
  const stack = [];
  const map = {};
  const tree = {};

  flatArr.forEach(node => {
    map[node.id] = node;
  });

  tree[rootId] = map[rootId];
  stack.push(tree[rootId]);

  while (stack.length) {
    const currentNode = stack.pop();
    const parentId = currentNode.id;
    flatArr.forEach(node => {
      if (node.parentId === parentId) {
        if (!currentNode.children) {
          currentNode.children = {};
        }
        currentNode.children[node.id] = node;
        stack.push(currentNode.children[node.id]);
      }
    });
  }

  return tree[rootId];
}

优点是性能较好,适合处理大规模数据。迭代算法的缺点是代码较为复杂,需要额外维护栈结构

哈希表

除了上述两种方法,我们还可以使用哈希表来解决这个问题。我们可以先将所有节点添加到哈希表中,以节点ID为键,节点对象为值。然后我们可以逐个遍历所有节点,对于每个节点,我们可以通过它的父节点ID在哈希表中查找对应的父节点,然后将其添加到父节点的子节点中。这样我们就可以使用哈希表来构建出一棵完整的树形结构。

function flatToTree(flatArr, rootId) {
  const map = {};
  flatArr.forEach(node => {
    map[node.id] = node;
  });

  const tree = {};
  flatArr.forEach(node => {
    if (node.id === rootId) {
      tree[node.id] = node;
    } else {
      const parent = map[node.parentId];
      if (!parent.children) {
        parent.children = {};
      }
      parent.children[node.id] = node;
    }
  });

  return tree[rootId];
}

Map算法的优点是性能较好,代码简洁易懂,适合处理大规模数据。缺点是需要额外创建Map对象,空间复杂度较高

总结

扁平数据结构转Tree是一个常见的算法问题,解决这个问题有多种方法。本文从递归、栈和哈希表三个角度探讨了如何解决这个问题,可以看出三种实现扁平数据结构转换为树形结构的算法各有优缺点。递归算法简单易懂但性能较差,迭代算法性能较好但代码较为复杂,Map算法性能较好但空间复杂度较高。综合来看,Map算法是性能最好的写法。

工作中掌握了数据结构与算法,你看待问题的深度,解决问题的角度就会完全不一样。因为这样的你,就像是站在巨人的肩膀上,拿着生存利器行走世界。