18

Half a decade ago I was sitting in a data structures class where the professor offered extra credit if anyone could traverse a tree without using recursion, a stack, queue, etc. (or any other similar data structures) and just a few pointers. I came up with what I thought was an obvious answer to that question which was ultimately accepted by the professor. I was sitting in a discrete math class with another professor in the same department--and he asserted that it was impossible to traverse a tree without recursion, a stack, queue, etc., and that my solution was invalid.

So, is it possible, or impossible? Why or why not?

Edit: To add some clarification, I implemented this on a binary tree which had three elements-- the data stored at each node and pointers to two children. My solution could be extended to n-ary trees with only a few changes.

My data structures teacher did not put any constraints against mutating the tree, and indeed I found out later that his own solution was to use the child pointers to point back up the tree on his way down. My discrete math professor said any mutation of a tree means that it is no longer a tree according to the mathematical definition of a tree, his definition would also preclude any pointers to parents--which would match the case where I solved it above.

3 Answers3

22

A lot of research in this area has been done, motivated by method of "cheaply" traversing trees and general list structures in the context of garbage collection.

A threaded binary tree is an adapted representation of binary trees where some nil-pointers are used to link to successor nodes in the tree. This extra information can be used to traverse a tree without stack. However, an extra bit per node is necessary to distinguish threads from child-pointers. Wikipedia:Tree_traversal

As far as I know binary trees implemented using pointers in the usual fashion (left and right pointer per node) can be traversed using the method of threads, in a method attributed to Morris. The NIL-pointers are temporarily re-used to thread a path back to the root. The clever part is that during traversal one can distinguish the original edges from the temporary thread-links, using the way they form cycles in the tree).

Good part: no extra data structure. Bad part: slightly cheating, the stack is inside the tree in a clever way. Very clever.

A proof of the hidden stack is shown in P. Mateti and R. Manghirmalani: Morris's Tree Traversal Algorithm Reconsidered DOI:10.1016/0167-6423(88)90063-9

J.M. Morris: Traversing binary trees simply and cheaply. IPL 9 (1979) 197-200 DOI:10.1016/0020-0190(79)90068-1

Then there also is Lindstrom scanning. This method "rotates" the three pointers involved in each node (parent and two children). If you want to perform any decent pre-order or post-order algorithms you need extra bits per node. If you just want to visit all the nodes (three times, but you never know which visit you perform) then it can be done without the bits.

G. Lindstrom: Scanning list structures without stacks or tag bits. IPL 2 (1973) 47-51. DOI:10.1016/0020-0190(73)90012-4

Perhaps the most straightforward way is a method by Robson. Here the stack needed for the classic algorithm is threaded through the leaves.

J.M. Robson: An improved algorithm for traversing binary trees without auxiliary stack IPL 1 (1973) 149-152. 10.1016/0020-0190(73)90018-5

IPL = Information Processing Letters

vonbrand
  • 14,204
  • 3
  • 42
  • 52
Hendrik Jan
  • 31,459
  • 1
  • 54
  • 109
6

I suppose that each node has a pointer to its parent (unless it's the root), as well as to its first child (if any), and its child has a pointer to its next sibling (if any). You can now simulate your favorite traversal order. You just need to come up with a rule of selecting the next node. For example, suppose you want to simulate postorder. Your first node is the "leftmost descendant", which can be obtained by starting at the root, and repeatedly moving to the first child. Now suppose that you're at some node $v$. If you have a next sibling, then you output the leftmost descendant of that sibling. If you don't have a next sibling, then you output the parent. If you don't have a parent, you're done.

Yuval Filmus
  • 280,205
  • 27
  • 317
  • 514
0

My solution was a bredth-first traversal using nested for-loops to brute force the tree. This isn't efficient by any means, and indeed a recursive data-structure like a tree is begging for a recursive traversal, but the question wasn't whether a tree could be traversed efficiently, is was whether it was even possible.

Pseudocode:
root = pointer root 
depth = integer 0
finished = bool false
//If we n-ary tree also track how many children have been found 
//on the node with the most children for the purposes of this psuedocode 
//we'll assume a binary tree and insert a magic number of 2 so that we 
//can use bitwise operators instead of integer division 
while(!finished)
    ++depth
    treePosition = pointer root
    finished = true;
    for i := 0..2**depth
        for j := 0..depth
            if (i & j) //bitwise operator explained below
                // if right child doesn't exist break the loop
                treePosition = treePosition.rightChild
            else
                // if left child doesn't exist break the loop
                treePosition = treePosition.leftChild
        if j has any children
            finished = false
            do anything else you want when visiting the node

For the first few levels it would look like this, as you can see, the bitwise operator in the pseudocode simply decides a left or right turn on a binary tree:

2**1       0               1
2**2   00      01      10      11
2**3 000 001 010 011 100 101 110 111

For n-ary, you would take i%(maxChildren**j)/j to determine which path to take between 0 and maxChildren.

At each node on n-ary you would also need to check to see if the number of the children is greater than maxChildren and update it appropriately.