繁体   English   中英

使用尾部递归实现javascript函数

[英]Implement javascript function using tail recursion

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

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

请指教 :)

 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); 

您的函数没有尾部调用,并且在所有情况下都不会,因为您多次调用了递归调用:请记住,尾部调用优化基本上意味着函数变成了循环,...对于这个案例。

也就是说,与其递归地查找所有嵌套元素并多次遍历数组,而使用id来反对Map,则只需要迭代两次:一次构建Map,第二次链接每个它的父元素。 可以在这里找到一个很好的实现。

这将是一个尾声版本(尽管我只是在这里使用循环):

 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
 }

“尾部调用”是对函数的调用,该函数在另一个函数中作为最后一个事物出现(特别是,所有返回值都转发给调用者)。

例如:

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 ({
    ...

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

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

以下方法使用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; } 

您可以通过将对象中的项目(parent_id)分组一次,并在需要该父级的子级时检索它,而不用在每次递归中都进行搜索(查找或过滤),从而降低代码的时间复杂度 ,从而优化代码。

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');

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM