Javascript 无需创建新节点的递归后序树遍历

Javascript 无需创建新节点的递归后序树遍历,javascript,recursion,tree,tail-recursion,postorder,Javascript,Recursion,Tree,Tail Recursion,Postorder,我想定义一个通用的尾部递归树遍历,它适用于所有类型的多路树。这对预顺序和级别顺序都很好,但是我在实现后顺序遍历时遇到了麻烦。以下是我正在使用的多路树: 所需订单:EKFBCGHIJDA 只要我不关心尾部递归,后序遍历就很容易: const postOrder=([x,xs])=>{ xs.forEach(邮购); log(`${x}`); }; 常量节点=(x,…xs)=>([x,xs]); 常量树=节点(“a”, 节点(“b”, 节点(“e”), 节点(“f”, 节点(“k”)), 节点(

我想定义一个通用的尾部递归树遍历,它适用于所有类型的多路树。这对预顺序和级别顺序都很好,但是我在实现后顺序遍历时遇到了麻烦。以下是我正在使用的多路树:

所需订单:EKFBCGHIJDA

只要我不关心尾部递归,后序遍历就很容易:

const postOrder=([x,xs])=>{
xs.forEach(邮购);
log(`${x}`);
};
常量节点=(x,…xs)=>([x,xs]);
常量树=节点(“a”,
节点(“b”,
节点(“e”),
节点(“f”,
节点(“k”)),
节点(“c”),
节点(“d”,
节点(“g”),
节点(“h”),
节点(“i”),
节点(j);

邮购(树)
我们从编写
Node.value
Node.children
开始,它们从您的节点获取两个值

// -- Node -----------------------------------------------

const Node = (x, ...xs) =>
  [ x, xs ]

Node.value = ([ value, _ ]) =>
  value

Node.children = ([ _, children ]) =>
  children
接下来,我们创建一个泛型
迭代器
类型。这一个模仿了原生iterable行为,只有我们的迭代器是持久的(不可变的)

最后,我们可以实现
PostorderIterator

const PostorderIterator = (node = Empty, backtrack = Iterator (), visit = false) =>
  Iterator (() =>
    visit
      ? Yield (node, backtrack)
      : isEmpty (node)
        ? backtrack.next ()
        : Node.children (node)
            .reduceRight ( (it, node) => PostorderIterator (node, it)
                         , PostorderIterator (node, backtrack, true)
                         )
            .next ())
我们可以在这里看到它与您的
树一起工作

// -- Demo ---------------------------------------------

const tree =
  Node ("a",
    Node ("b",
      Node ("e"),
      Node ("f",
        Node ("k"))),
    Node ("c"),
    Node ("d",
      Node ("g"),
      Node ("h"),
      Node ("i"),
      Node ("j")));

const postOrderValues =
  Array.from (Generator (PostorderIterator (tree)), Node.value)

console.log (postOrderValues)
// [ 'e', 'k', 'f', 'b', 'c', 'g', 'h', 'i', 'j', 'd', 'a' ]
程序演示

/--Node----------------------------------------------
常量节点=(x,…xs)=>
[x,xs]
Node.value=([value,)]=>
价值
Node.children=([\ux,children])=>
儿童
//--空的---------------------------------------------
常数为空=
符号()
常数isEmpty=x=>
x==空
//--迭代器------------------------------------------
constyield=(value=Empty,it=Iterator())=>
isEmpty(值)
? {完成:正确}
:{done:false,value,next:it.next}
常量迭代器=(下一步=产量)=>
({next})
常量生成器=函数*(它=迭代器())
{
while(it=it.next())
如果(完成)
打破
其他的
让出它的价值
}
const PostorderIterator=(node=Empty,backtrack=Iterator(),visit=false)=>
迭代器(()=>
参观
?产量(节点、回程)
:isEmpty(节点)
?backtrack.next()
:Node.children(节点)
.reduceRight((it,节点)=>PostorderIterator(节点,it)
,PostorderIterator(节点,回溯,true)
)
.next())
//--演示-------------------------------------------
常数树=
节点(“a”,
节点(“b”,
节点(“e”),
节点(“f”,
节点(“k”)),
节点(“c”),
节点(“d”,
节点(“g”),
节点(“h”),
节点(“i”),
节点(j);
常量PostOrderValue=
Array.from(生成器(PostorderIterator(树)),Node.value)
console.log(postOrderValues)

//['e','k','f','b','c','g','h','i','j','d','a']
堆栈安全

我的第一个答案是通过编写我们自己的函数迭代器协议来解决这个问题。诚然,我渴望分享这种方法,因为这是我过去探索过的东西。编写自己的数据结构真的很有趣,它可以为您的问题提供创造性的解决方案——如果我先给出简单的答案,您会感到厌烦,不是吗

const Empty =
  Symbol ()

const isEmpty = x =>
  x === Empty

const postOrderFold = (f = (a, b) => a, acc = null, node = Empty) =>
{
  const loop = (acc, [ node = Empty, ...nodes ], cont) =>
    isEmpty (node)
      ? cont (acc)
      : ???
  return loop (acc, [ node ], identity)
}

const postOrderValues = (node = Empty) =>
  postOrderFold ((acc, node) => [ ...acc, Node.value (node) ], [], node)

console.log (postOrderValues (tree))
// [ 'e', 'k', 'f', 'b', 'c', 'g', 'h', 'i', 'j', 'd', 'a' ]
下面为其他读者提供了完整的解决方案

const节点=(x,…xs)=>
[x,xs]
Node.value=([value,)]=>
价值
Node.children=([\ux,children])=>
儿童
常数为空=
符号()
常数isEmpty=x=>
x==空
常量标识=x=>
x
//尾部递归
const postOrderFold=(f=(a,b)=>a,acc=null,node=Empty)=>
{
常量循环=(acc,[节点=空,…节点],cont)=>
等空(节点)
?续(附件)
:loop(acc,Node.children(Node),nextac=>
循环(f(nextac,node),nodes,cont))
返回回路(acc,[节点],标识)
}
const postOrderValues=(节点=空)=>
postOrderFold((acc,node)=>[…acc,node.value(node)],[],node)
常数树=
节点(“a”,
节点(“b”,
节点(“e”),
节点(“f”,
节点(“k”)),
节点(“c”),
节点(“d”,
节点(“g”),
节点(“h”),
节点(“i”),
节点(“j”))
console.log(postOrderValues(树))

//这是一个伟大的工程。但是,我不确定迭代器/生成器在语法和控制流方面的额外复杂性是否值得(如您所知,使用蹦床可以实现堆栈安全w/o TCO)。可理解性和简单性是开发人员也应该考虑的属性。这是正确的平衡。几天前得了瘟疫,现在无法处理任何信息。你是最棒的,谢谢!
const Empty =
  Symbol ()

const isEmpty = x =>
  x === Empty

const postOrderFold = (f = (a, b) => a, acc = null, node = Empty) =>
{
  const loop = (acc, [ node = Empty, ...nodes ], cont) =>
    isEmpty (node)
      ? cont (acc)
      : ???
  return loop (acc, [ node ], identity)
}

const postOrderValues = (node = Empty) =>
  postOrderFold ((acc, node) => [ ...acc, Node.value (node) ], [], node)

console.log (postOrderValues (tree))
// [ 'e', 'k', 'f', 'b', 'c', 'g', 'h', 'i', 'j', 'd', 'a' ]