【问题标题】:Implement javascript function using tail recursion使用尾递归实现javascript函数
【发布时间】:2019-05-08 18:47:46
【问题描述】:

我有一个表示树的平面数组,我想使用尾递归构建一个嵌套对象。

我有以下代码可以运行并生成所需的输出,但我不确定它是否是尾递归的正确实现。

请指教:)

const myArray = [
  { id: 'root' },
  { id: 0, parent: 'root' },
  { id: 1, parent: 'root' },
  { id: 2, parent: 0 },
  { id: 3, parent: 1 },
  { id: 4, parent: 2 },
  { id: 5, parent: 1 },
  { id: 6, parent: 4 },
  { id: 7, parent: 0 },
  { id: 8, parent: 0 },
];


function makeNestedTreeFromArray(array, id, children) {
  if (children.length <= 0) {
    return array.find(entry => entry.id === id);
  }
  return ({
    ...array.find(entry => entry.id === id),
    children: children.map(child => makeNestedTreeFromArray(
      array,
      child.id,
      array.filter(entry => entry.parent === child.id),
    ))
  });
}

const myTree = makeNestedTreeFromArray(
  myArray,
  'root',
  myArray.filter(entry => entry.parent === 'root'),
);

console.log(myTree);

【问题讨论】:

  • 不,它不是,顺便说一句,这是一个非常糟糕的算法,需要大量迭代才能找到孩子。对于尾递归,您需要通过函数本身返回最后一个表达式,而不是在某些对象之间或内部。
  • 检查这个 SO 答案,我认为就复杂性和递归而言,这是最好的解决方案(它假设列表已排序) - stackoverflow.com/a/18018037/2332159

标签: javascript recursion tail-recursion


【解决方案1】:

尾递归的基本原理是返回具有更改参数的相同函数。这允许用函数的新调用替换最后一个堆栈条目,而不会增加堆栈大小。

以下方法使用TCO 并返回函数调用,并使用标准退出条件从函数顶部的递归函数返回。

该算法仅访问每个项目并构建具有多个根的树。最后只返回想要的根。这种方法适用于未排序的数据,因为对于每个节点,idparent 的信息都被使用并保留了它们的关系。

function getTree(data, root, index = 0, tree = {}) {
    var o = data[index];
    if (!o) return tree[root];
    Object.assign(tree[o.id] = tree[o.id] || {}, o);
    tree[o.parent] = tree[o.parent] || {};
    tree[o.parent].children = tree[o.parent].children || [];
    tree[o.parent].children.push(tree[o.id]);
    return getTree(data, root, index + 1, tree);
}

const
    data = [{ id: 'root' }, { id: 0, parent: 'root' }, { id: 1, parent: 'root' }, { id: 2, parent: 0 }, { id: 3, parent: 1 }, { id: 4, parent: 2 }, { id: 5, parent: 1 }, { id: 6, parent: 4 }, { id: 7, parent: 0 }, { id: 8, parent: 0 }],
    tree = getTree(data, 'root');

console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

    【解决方案2】:

    您的函数没有尾调用,并且在所有情况下都不会,因为您多次调用递归调用:请记住,尾调用优化基本上意味着将函数变成一个循环,...在这种情况下是不可能的。

    也就是说,与其递归查找所有嵌套元素并多次迭代数组,不如使用 id 对象 Map,然后您只需要迭代两次:一次构建 Map,第二次将每个元素链接到它的父元素。可以在 here 找到一个很好的实现。

    这将是一个尾调用版本(不过我只是在这里使用一个循环):

     function listToTree([el, ...rest], parent = new Map, roots = []) {
       if(el.parentID)
          parent.get(el.parentID).children.push(el);
       else roots.push(el);
    
       parent.set(el.id, el);
       el.children = [];
    
       if(!rest.length) return roots;
    
       return listToTree(rest, parent, roots); // A proper tail call: This can be turned into a loop
     }
    

    【讨论】:

      【解决方案3】:

      “尾调用”是对函数的调用,在另一个函数中作为最后一件事发生(特别是,任何返回值都会转发给调用者)。

      例如:

      function foo() {
          ...
          return bar("hi");  // a tail call to bar
      }
      

      尾递归意味着它是对函数本身的尾调用,即递归尾调用:

      function foo() {
          ...
          return foo();  // a recursive tail call, or: tail recursion
      }
      

      这不适用于您的代码,因为您有

      function makeNestedTreeFromArray(array, id, children) {
        ...
        return ({
          ...
      

      即您的函数返回一个新对象,而不是另一个函数调用的结果(更不用说对自身的调用了)。

      【讨论】:

        【解决方案4】:

        您可以优化代码,方法是降低代码的时间复杂度而不是在每次递归中搜索(查找或过滤)它。

        var listTree = (array, parentId, searchObj)=>{
          if(searchObj === undefined) {
              // Create the searchObject only once. Reducing time complexity of the code
              searchObj = {};
              array.forEach(data => {
                if(searchObj[data.parent]){
                  searchObj[data.parent].push(data)
                }else {
                  searchObj[data.parent] = [data];
                }
              });
           }
           let children = searchObj[parentId];
           // return empty array if no parent is retrieved.
           return !children ? [] : children.map(single=>{
              // Pass in the same searchObj so the the search filter is not repeated again
              single.children = listTree(array, single.id, searchObj)
              return single;
           })
        }
        
        // Run the code
        listTree(myArray, 'root');
        
        

        【讨论】:

          猜你喜欢
          • 2019-04-07
          • 2023-03-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-09-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多