views:

543

answers:

10

What is the best way to visit all the nodes of a linked tree (all nodes have references to parent and all children, root nodes have null as parent), so that no node is visited before any of its ancestors? Brownie points for non-recursive.

A: 

Build up a list of nodes at the root (level 0), iterate over each node in turn and look for direct children (whose parent node is the node we are currently looking from) (level 1), when finished with level 0 move on to iterating level 1, and so on until you have no remaining unvisited nodes.

Sychare Jedko
+1  A: 

Use a set of nodes. Put the root in the set to start. Then in a loop, pull a node out of the set, visit it, then put its children in the set. When the set is empty, you are done.

Keith Randall
You want the data structure to be FIFO, not just any old container, to guarantee the preorder condition.
Jim Lewis
There was no such requirement in the question.
Keith Randall
A: 

In pseudocode:

currentList = list( root )
nextList = list()
while currentList.count > 0:
    foreach node in currentList:
        nextList.add(node.children)
    currentList = nextList
JSBangs
+3  A: 

You're looking for a preorder traversal. I think you can do it non-recursively with a queue:. In pseudocode:

Create an empty queue, then push the root node.

while nonempty(q)
  node = pop(q)
  visit(node)
  foreach child(node)
    push(q,child)
Jim Lewis
That would be a *non-recirsive implemenation* of a *recursive algorithm*. Replacing implied stack with explicit stack doesn't turn a recursive algorithm into a non-recursive one. In fact, it doesn't change the algorithm at all. What you have above is obviously recursive (as far as algorithm is concerned).
AndreyT
Typically when people say they don't want recursion, they're referring to function-level recursion. This satisfies that condition.
JSBangs
Well, sometimes yes. However, the problem we are considering here is specifically crafted to allow truly non-recursive solution (i.e. non-recursive algorithm). The dead giveaway is the presence of parent pointers. Your "non-recursive" solution doesn't use parent pointers. Don't you wonder why they are even there? They are there specifically to allow a true non-recursive solution, the one with O(1) memory requirement.
AndreyT
But most of the time - no. Typically when people say they don't want recursion, they mean that they don't want the memory requrements of recursion, i.e. they don't want to have a non-constant-sized data structure for storing "subproblems", be it FIFO, LIFO or whatever else.
AndreyT
@AndreT: I don't think I've ever heard "non-recursive" used to mean "constant space requirement" as you seem to believe. So I have to disagree that your usage is "typical".
Jim Lewis
@Jim Lewis: Firstly, it is not just "constant space requirement" in general (I never said that). It is constant number of "scheduled" subtasks (and, therefore, constant space for storing these subtasks).Secondly, that's too bad that you never heard about it. Becuase this is actually a defining property of non-recursive *algorithm*. The lack of understanding of what the term "recursive algorithm" means, and the lack of understanding of the difference between "algorithm" and "implementation" is rather appaling these days.
AndreyT
When people come up with a non-recursive *implementation* by implementing recursive *algorithm* with a hand-made stack (as opposed to using a functional stack), and then call it *non-recursive algorithm*... That's just... well... ridiculous, for the lack of better word.
AndreyT
+5  A: 

Breadth First Search

Mercurybullet
+4  A: 

Pseudo code:

NodesToVisit = some stack or some list
NodesToVisit.Push(RootNode)

While NodesToVisit.Length > 0
{
   CurNode = NodesToVisit.Pop()
   For each CHild C in CurNode
        NodesToVIsit.Push(C)
   Visit CurNode (i.e. do whatever it needs to have done
}

Edit: Recursive or not?
To be technically correct, and as pointed out by AndreyT and others in this post, this approach is a form of a recursive algorithm, whereby an explicitly managed stack is used in lieu of the CPU stack and where the recursion takes place at the level of the While loop. This said, it differs from a recursive implementation per se in a couple of subtle yet significant ways:

  • Only the "variables" are pushed onto the stack; there is no "stack frame" and associated return address on the stack, the only "return address" is implicit to the while loop, and there is but one instance of it.
  • The "stack" could be used as a list whereby the next "frame" could be taken anywhere in the list, without braking the logic in any way.
mjv
OK. This wasn't an academic question. It was a practical question. This answers it in a satisfactory way without making me think or do any further reading. Thanks. (It will probably make me think later when I get the time, but that's fine... Useful, even...) Job's done. Thanks again :)
George
+1  A: 

If you start at the root node, and only visit the parents/children of nodes you have already visited, there is no way to traverse the tree such that you visit a node before visiting its ancestors.

Any sort of traversal, depth first (recursive/stack based), breadth first (queue based), depth-limited, or just pulling them out of an unordered set, will work.

The "best" method depends on the tree. Breadth first would work well for a very tall tree with few branches. Depth first would work well for trees with many branches.

Since the nodes actually have pointers to their parents, there is also a constant-memory algorithm, but it is much slower.

Artelius
Teh op said "*no* node is visited before its ancestors". So it's the other way around.
AndreyT
What's the other way round? Did you read my answer properly?
Artelius
Maybe not. I thought that in your first sentence you claimed that the problem cannot be solved, since the visitation order requrement (which, I assumed, you misunderstood) is impossible to satisfy.
AndreyT
I was making the point that ANY traversal (starting from the root node) would meet the requirement.
Artelius
Yeah, I get it now.
AndreyT
+2  A: 

If you have links to all children and to the parent as well, then non-recursive algorithm is rather trivial. Just forget that you have a tree. Think of it is a labirynth where each parent-child link is an ordinary bi-directional corridor from one junction to the other. All you need to do to walk the entire labyrinth is to turn into the next corridor on the left at each junction. (Alternatively, think of it as walking the labyrinth with your left hand always touching the wall on the left side). If you start from the root junction (and move in any direction), you'll walk the entire tree always visiting parents before children. Each "corridor" in this case will be travelled twice (in one direction and in the other), and each "junction" (node) will be visited as many times as many "corridors" join it.

AndreyT
This is the constant memory algorithm I mentioned.
Artelius
+1  A: 

I would disagree with breadth first search as space complexity is often the bane of that specific search algorithm. Possibly using the iterative deepening algorithm is a better alternative for this type of use, and it covers the same type of traversal as breadth first search. There are minor differences in dealing with the fringe from breadth-first search, it shouldn't be too hard to (pseudo) code out though.

Reference: http://en.wikipedia.org/wiki/Iterative%5Fdeepening

mduvall
+1 because of your consideration space complexity - but why not just use a depth-first search?
Artelius
Many trees in practice tend to be deeper than they are 'wider', esp. in AI decision-making processes. The question does not state whether the tree is finite, but you could run into a loop. One of the reasons iterative deepening is liked is that it is complete (will find a solution).
mduvall
A: 

Here's a truly non-recursive approach: no stack, constant space. This Python code assumes that each node contains a list of children, and that the node objects do not define equality, so that the 'index' function is comparing identities:

def walkTree(root, visit_func):
    cur  = root
    nextChildIndex = 0

    while True:
        visit_func(cur)

        while nextChildIndex >= len(cur.children) and cur is not root:
            nextChildIndex = cur.parent.children.index(cur) + 1
            cur  = cur.parent

        if nextChildIndex >= len(cur.children):
            break

        cur = cur.children[nextChildIndex]
        nextChildIndex = 0

I'm sure it could be polished up a bit, made more concise and easier to read, but that's the gist.

swillden