Recursive post order tree traversal without creating new nodes

此生再无相见时 提交于 2019-12-04 18:24:33

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.

Later does not have a proper tail call but I thought the solution was too neat not to share it

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

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

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

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

Next, we create a generic Iterator type. 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

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

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

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

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. 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' ]
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!