简体   繁体   English

递归邮政订单树遍历而不创建新节点

[英]Recursive post order tree traversal without creating new nodes

I want to define a generalized tail recursive tree traversal that works for all kinds of multi-way trees. 我想定义一个适用于各种多路树的通用尾递归树遍历。 This works fine with pre-order and level-order, but I'm having trouble to implement post order traversals. 这适用于预订和水平顺序,但我无法实现后订单遍历。 Here is the multi-way tree I am working with: 这是我正在使用的多向树:

多路树

Desired order: EKFBCGHIJDA 期望的订单:EKFBCGHIJDA

As long as I don't care about tail recursion post order traversal is easy: 只要我不关心尾递归,命令遍历很容易:

 const postOrder = ([x, xs]) => { xs.forEach(postOrder); console.log(`${x}`); }; const Node = (x, ...xs) => ([x, xs]); const tree = Node("a", Node("b", Node("e"), Node("f", Node("k"))), Node("c"), Node("d", Node("g"), Node("h"), Node("i"), Node("j"))); postOrder(tree); 

The tail recursive approach, on the other hand, is quite cumbersome: 另一方面,尾递归方法非常麻烦:

 const postOrder = (p, q) => node => { const rec = ({[p]: x, [q]: forest}, stack) => { if (forest.length > 0) { const [node, ...forest_] = forest; stack.unshift(...forest_, Node(x)); return rec(node, stack); } else { console.log(x); if (stack.length > 0) { const node = stack.shift(); return rec(node, stack); } else return null; } }; return rec(node, []); }; const Node = (x, ...xs) => ([x, xs]); const tree = Node("a", Node("b", Node("e"), Node("f", Node("k"))), Node("c"), Node("d", Node("g"), Node("h"), Node("i"), Node("j"))); postOrder(0, 1) (tree); 

In particular, I'd like to avoid creating new nodes so that I can traverse arbitrary trees without having to know anything about their constructors. 特别是,我想避免创建新节点,以便我可以遍历任意树而无需了解其构造函数。 Is there a way to do this and still remain tail recursive? 有没有办法做到这一点仍然保持尾递归?

stack-safe 堆栈安全

My first answer solves this problem by writing our own functional iterator protocol. 我的第一个答案是通过编写我们自己的函数迭代器协议来解决这个问题。 Admittedly, I was eager to share this approach as it's something I've explored in the past. 不可否认,我渴望分享这种方法,因为这是我过去探索过的。 Writing your own data structures is really fun and it can yield creative solutions to your problem - and you'd be bored if I gave out the easy answers first, wouldn't you? 编写自己的数据结构非常有趣,它可以为您的问题提供创造性的解决方案 - 如果我首先给出简单的答案,您会感到无聊,不是吗?

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' ]

Full solution included below for other readers... 以下为其他读者提供完整解决方案......

 const Node = (x, ...xs) => [ x, xs ] Node.value = ([ value, _ ]) => value Node.children = ([ _, children ]) => children const Empty = Symbol () const isEmpty = x => x === Empty const identity = x => x // tail recursive const postOrderFold = (f = (a, b) => a, acc = null, node = Empty) => { const loop = (acc, [ node = Empty, ...nodes ], cont) => isEmpty (node) ? cont (acc) : loop (acc, Node.children (node), nextAcc => loop (f (nextAcc, node), nodes, cont)) return loop (acc, [ node ], identity) } const postOrderValues = (node = Empty) => postOrderFold ((acc, node) => [ ...acc, Node.value (node) ], [], node) const tree = Node("a", Node("b", Node("e"), Node("f", Node("k"))), Node("c"), Node("d", Node("g"), Node("h"), Node("i"), Node("j"))) console.log (postOrderValues (tree)) // [ 'e', 'k', 'f', 'b', 'c', 'g', 'h', 'i', 'j', 'd', 'a' ] 

mutual recursion 相互递归

Somehow it's your questions that allow me to canvas my most inspired works. 不知何故,这是你的问题,让我画出我最有灵感的作品。 Back in the headspace of tree traversals, I came up with this sort of pseudo-applicative sum type Now and Later . 回到树遍历的顶空中,我想出了这种伪应用和类型Now and Later

Later does not have a proper tail call but I thought the solution was too neat not to share it Later没有正确的尾部呼叫,但我认为解决方案太整洁,不能分享它

const Empty =
  Symbol ()

const isEmpty = x =>
  x === Empty

const postOrderFold = (f = (a, b) => a, acc = null, node = Empty) =>
{
  const Now = node =>
    (acc, nodes) =>
      loop (f (acc, node), nodes)

  const Later = node =>
    (acc, nodes) =>
      loop (acc, [ ...Node.children (node) .map (Later), Now (node), ...nodes ])

  const loop = (acc, [ reducer = Empty, ...rest ]) =>
    isEmpty (reducer)
      ? acc
      : reducer (acc, rest)

  // return loop (acc, [ ...Node.children (node) .map (Later), Now (node) ])
  // or more simply ...
  return Later (node) (acc, [])
}

Mutual recursion demonstration 相互递归演示

 const Node = (x, ...xs) => [ x, xs ] Node.value = ([ value, _ ]) => value Node.children = ([ _, children ]) => children const Empty = Symbol () const isEmpty = x => x === Empty const postOrderFold = (f = (a, b) => a, acc = null, node = Empty) => { const Now = node => (acc, nodes) => loop (f (acc, node), nodes) const Later = node => (acc, nodes) => loop (acc, [ ...Node.children (node) .map (Later), Now (node), ...nodes ]) const loop = (acc, [ reducer = Empty, ...rest ]) => isEmpty (reducer) ? acc : reducer (acc, rest) // return loop (acc, [ ...Node.children (node) .map (Later), Now (node) ]) // or more simply ... return Later (node) (acc, []) } const postOrderValues = (node = Empty) => postOrderFold ((acc, node) => [ ...acc, Node.value (node) ], [], node) const tree = Node("a", Node("b", Node("e"), Node("f", Node("k"))), Node("c"), Node("d", Node("g"), Node("h"), Node("i"), Node("j"))) console.log (postOrderValues (tree)) // [ 'e', 'k', 'f', 'b', 'c', 'g', 'h', 'i', 'j', 'd', 'a' ] 

We start by writing Node.value and Node.children which get the two values from your Node 我们首先编写Node.valueNode.children ,它们从您的Node获取两个值

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

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

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

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

Next, we create a generic Iterator type. 接下来,我们创建一个通用的Iterator类型。 This one imitates the native iterable behavior, only our iterators are persistent (immutable) 这个模仿本机可迭代行为,只有我们的迭代器是持久的(不可变的)

// -- Empty ----------------------------------------------

const Empty =
  Symbol ()

const isEmpty = x =>
  x === Empty

// -- Iterator -------------------------------------------

const Yield = (value = Empty, it = Iterator ()) =>
  isEmpty (value)
    ? { done: true }
    : { done: false, value, next: it.next }

const Iterator = (next = Yield) =>
  ({ next })

const Generator = function* (it = Iterator ())
{
  while (it = it.next ())
    if (it.done)
      break
    else
      yield it.value
}

Lastly, we can implement PostorderIterator 最后,我们可以实现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 ())

And we can see it working with your tree here 我们可以看到它在这里与您的tree一起工作

// -- 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' ]

Program demonstration 程序演示

 // -- Node ---------------------------------------------- const Node = (x, ...xs) => [ x, xs ] Node.value = ([ value, _ ]) => value Node.children = ([ _, children ]) => children // -- Empty --------------------------------------------- const Empty = Symbol () const isEmpty = x => x === Empty // -- Iterator ------------------------------------------ const Yield = (value = Empty, it = Iterator ()) => isEmpty (value) ? { done: true } : { done: false, value, next: it.next } const Iterator = (next = Yield) => ({ next }) const Generator = function* (it = Iterator ()) { while (it = it.next ()) if (it.done) break else yield it.value } 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' ] 

The variadic children field makes the algorithm a little more complicated compare to a Node type that only has left and right fields 与仅具有leftright字段的节点类型相比,可变参数children字段使算法稍微复杂一些

The simplified implementation of these iterators makes them a bit easier to compare. 这些迭代器的简化实现使它们更容易比较。 Writing support for variadic children in the other iterators is left as an exercise to the reader 在其他迭代器中为可变子项写入支持留给读者练习

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

const Node = (value, left = Empty, right = Empty) =>
  ({ value, left, right })

// -- Iterators ----------------------------------------

const PreorderIterator = (node = Empty, backtrack = Iterator ()) =>
  Iterator (() =>
    isEmpty (node)
      ? backtrack.next ()
      : Yield (node,
          PreorderIterator (node.left,
            PreorderIterator (node.right, backtrack))))

const InorderIterator = (node = Empty, backtrack = Iterator (), visit = false) =>
  Iterator (() =>
    visit
      ? Yield (node, backtrack)
      : isEmpty (node)
        ? backtrack.next ()
        : InorderIterator (node.left,
            InorderIterator (node,
              InorderIterator (node.right, backtrack), true)) .next ())

const PostorderIterator = (node = Empty, backtrack = Iterator (), visit = false) =>
  Iterator (() =>
    visit
      ? Yield (node, backtrack)
      : isEmpty (node)
        ? backtrack.next ()
        : PostorderIterator (node.left,
            PostorderIterator (node.right,
              PostorderIterator (node, backtrack, true))) .next ())

And a very special LevelorderIterator , just because I think you can handle it 还有一个非常特殊的LevelorderIterator ,因为我认为你可以处理它

const LevelorderIterator = (node = Empty, queue = Queue ()) =>
  Iterator (() =>
    isEmpty (node)
      ? queue.isEmpty ()
        ? Yield ()
        : queue.pop ((x, q) =>
            LevelorderIterator (x, q) .next ())
      : Yield (node,
          LevelorderIterator (Empty,
            queue.push (node.left) .push (node.right))))

// -- Queue ---------------------------------------------

const Queue = (front = Empty, back = Empty) => ({
  isEmpty: () =>
    isEmpty (front),
  push: x =>
    front
      ? Queue (front, Pair (x, back))
      : Queue (Pair (x, front), back),
  pop: k =>
    front ? front.right ? k (front.left, Queue (front.right, back))
                        : k (front.left, Queue (List (back) .reverse () .pair, Empty))
          : k (undefined, undefined)
})

// -- List ----------------------------------------------

const List = (pair = Empty) => ({
  pair:
    pair,
  reverse: () =>
    List (List (pair) .foldl ((acc, x) => Pair (x, acc), Empty)),
  foldl: (f, acc) =>
    {  
      while (pair)
        (acc = f (acc, pair.left), pair = pair.right)
      return acc
    }
})

// -- Pair ----------------------------------------------
const Pair = (left, right) =>
  ({ left, right })

Over-engineered? 过度设计? Guilty. 有罪。 You can swap out the interfaces above for nothing but JavaScript primitives. 除了JavaScript原语外,你可以换掉上面的接口。 Here we trade the lazy stream for an eager array of values 在这里,我们交换懒惰的流,以获得一系列渴望的值

const postOrderValues = (node = Empty, backtrack = () => [], visit = false) =>
  () => visit
      ? [ node, ...backtrack () ]
      : isEmpty (node)
        ? backtrack ()
        : Node.children (node)
            .reduceRight ( (bt, node) => postOrderValues (node, bt)
                         , postOrderValues (node, backtrack, true)
                         )
            ()

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

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

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