22

Right now I have

 private static void iterateall(BinaryTree foo) {
    if(foo!= null){
    System.out.println(foo.node);
    iterateall(foo.left);
    iterateall(foo.right);
   }
  }

Can you change it to Iteration instead of a recursion?

polygenelubricants
  • 376,812
  • 128
  • 561
  • 623
unj2
  • 52,135
  • 87
  • 247
  • 375
  • 17
    @Michael : I am at home but this is not related to my work. – unj2 May 31 '10 at 09:26
  • 3
    I voted up the previous comment, because it's full of WIN :) – mohdajami May 31 '10 at 09:30
  • Your DFS w/o a stack is completely broken. You *need* the stack (or, alternatively, a queue – but let’s ignore that for now), there’s no way around it. – Konrad Rudolph May 31 '10 at 09:31
  • @Konrad: Deleted it. Thanks for pointing it out. – unj2 May 31 '10 at 09:33
  • Most people use a binary tree to do an efficient insertion sort. Otherwise, they would use a collection. To print in sorted order, you should iterate left, print the node, then iterate right. – Ron May 31 '10 at 13:33

6 Answers6

57

What you're looking for is a successor algorithm.

Here's how it can be defined:

  • First rule: The first node in the tree is the leftmost node in the tree.
  • Next rule: The successor of a node is:
    • Next-R rule: If it has a right subtree, the leftmost node in the right subtree.
    • Next-U rule: Otherwise, traverse up the tree
      • If you make a right turn (i.e. this node was a left child), then that parent node is the successor
      • If you make a left turn (i.e. this node was a right child), continue going up.
      • If you can't go up anymore, then there's no successor

As you can see, for this to work, you need a parent node pointer.


Example:

alt text

  • First rule: The first node in the tree is the leftmost node in the tree: (1)
  • Next-U rule: Since (1) has no right subtree, we go up to (3). This is a right turn, so (3) is next.
  • Next-R rule: Since (3) has a right subtree, the leftmost node in that subtree is next: (4).
  • Next-U rule: Since (4) has no right subtree, we go up to (6). This is a right turn, so next is (6).
  • Next-R rule: Since (6) has a right subtree, the leftmost node in that subtree is next: (7).
  • Next-U rule: Since (7) has no right subtree, we go up to (6). This is a left turn, so we continue going up to (3). This is a left turn, so we continue going up to (8). This is a right turn, so next is (8).
  • Next-R rule: Since (8) has a right subtree, the leftmost node in that subtree is next: (10).
  • Next-R rule: Since (10) has a right subtree, the leftmost node in that subtree is next: (13).
  • Next-U rule: Since (13) has no right subtree, we go up to (14). This is a right turn, so next is (14).
  • Next-U rule: Since (14) has no right subtree, we go up to (10). This is a left turn, so we continue going up to (8). This is a left turn, so we want to continue going up, but since (8) has no parent, we've reached the end. (14) has no successor.

Pseudocode

Node getLeftMost(Node n)
  WHILE (n.leftChild != NULL)
    n = n.leftChild
  RETURN n

Node getFirst(Tree t)
  IF (t.root == NULL) RETURN NULL
  ELSE
     RETURN getLeftMost(t.root);

Node getNext(Node n)
  IF (n.rightChild != NULL)
     RETURN getLeftMost(n.rightChild)
  ELSE
     WHILE (n.parent != NULL AND n == n.parent.rightChild)
        n = n.parent;
     RETURN n.parent;

PROCEDURE iterateOver(Tree t)
  Node n = getFirst(t);
  WHILE n != NULL
     visit(n)
     n = getNext(n)

Java code

Here's a simple implementation of the above algorithm:

public class SuccessorIteration {
    static class Node {
        final Node left;
        final Node right;
        final int key;
        Node parent;
        Node(int key, Node left, Node right) {
            this.key = key;
            this.left = left;
            this.right = right;
            if (left != null) left.parent = this;
            if (right != null) right.parent = this;
        }
        Node getLeftMost() {
            Node n = this;
            while (n.left != null) {
                n = n.left;
            }
            return n;
        }
        Node getNext() {
            if (right != null) {
                return right.getLeftMost();
            } else {
                Node n = this;
                while (n.parent != null && n == n.parent.right) {
                    n = n.parent;
                }
                return n.parent;
            }
        }
    }
}

Then you can have a test harness like this:

static Node C(int key, Node left, Node right) {
    return new Node(key, left, right);
}
static Node X(int key)             { return C(key, null, null);  }
static Node L(int key, Node left)  { return C(key, left, null);  }
static Node R(int key, Node right) { return C(key, null, right); }
public static void main(String[] args) {
    Node n =
        C(8,
            C(3,
                X(1),
                C(6,
                    X(4),
                    X(7)
                )
            ),
            R(10,
                L(14,
                    X(13)
                )
            )
        );
    Node current = n.getLeftMost();
    while (current != null) {
        System.out.print(current.key + " ");
        current = current.getNext();
    }
}

This prints:

1 3 4 6 7 8 10 13 14 

See also

Community
  • 1
  • 1
polygenelubricants
  • 376,812
  • 128
  • 561
  • 623
17

Can you change it to Iteration instead of a recursion?

You can, using an explicit stack. Pseudocode:

private static void iterateall(BinaryTree foo) {
    Stack<BinaryTree> nodes = new Stack<BinaryTree>();
    nodes.push(foo);
    while (!nodes.isEmpty()) {
        BinaryTree node = nodes.pop();
        if (node == null)
            continue;
        System.out.println(node.node);
        nodes.push(node.right);
        nodes.push(node.left);
    }
}

But this isn’t really superior to the recursive code (except for the missing base condition in your code).

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 3
    "not superior": I agree. While its good to know that all recursive algorithms can be converted to iterative ones and how it is done, it often doesn't bring any significant advantages if it's actually done. – Joachim Sauer May 31 '10 at 09:36
  • Don't you want to push node.right first (so it gets popped last)? – ILMTitan Jun 01 '10 at 15:25
  • @ILMTitan: My code doesn’t preserve the order of traversal. You’re right that to do that, I need to invert the pushing order. Or in fact use a queue instead of a stack. – Konrad Rudolph Jun 01 '10 at 19:37
  • 1
    @JoachimSauer : `it often doesn't bring any significant advantages if it's actually done` -- thats observably wrong in every regard. Recursive algorithms are **much** slower **by design** in almost all languages and environments. They also use **much** more available space and can easily crash the application. Basic facts. The only advantage of recursive algorithms over iterative ones is the readability and ease of implementation ... some data structures require quite a bit of engineering to be traversed iteratively whereas they could be traversed much easier with recursion. – specializt Mar 26 '19 at 06:39
  • 3
    @specializt Basically every single thing you’ve said is wrong. You seem to equate replacing recursion with iteration with replacing a naïve algorithm by a more sophisticated one. This is *sometimes* the natural thing to do (calculating Fibonacci numbers being the typical example) but it’s not a necessary consequence. Iterative algorithms can be (memory/time) inefficient. Conversely, recursion can be efficient. For instance, a natural, recursive computation of Fibonacci numbers can be made as efficient as the canonical iterative version (running in O(n) time, using O(1) space). – Konrad Rudolph Mar 26 '19 at 11:35
  • 1
    i think you should lookup the terms you're using - and after that, you should probably look up how CPUs and modern computers in general work. The reason for recursion **always** being less efficient and more error-prone, compared to equivalent iterative algorithms is simple : Firstly, recursion causes the stack to build up if you dont measure the maximum stack size for **your** application and stop recursing. Secondly, recursion causes context change - which is **always** rather slow, thats even a truth of central processing units themselves. – specializt Mar 26 '19 at 11:48
  • 1
    Also : absolutely every big-O number of your beloved recursive algorithms is also true for iterative equivalents. Next time : dont assume things nor repeat stuff you might've heard or read somewhere without digging into the topic deeply. And another thing: your comparison doesnt make any sense whatsoever - nobody is even remotely talking about naive and sophisticated algorithms ... but thanks for trying, im guessing thats a weird attempt at the "strawman argument" or something. – specializt Mar 26 '19 at 11:50
  • @specializt Trust me, I know very well what I’m talking about here, and you’re starting to embarrass yourself with your assumptions about what I do and do not know. If you think that “recursion [always] causes the stack to build up” or that it causes context switches then you simply don’t know a lot about how recursion can be (and often is) implemented. – Konrad Rudolph Mar 26 '19 at 12:01
  • @specializt That’s simply a weird accusation. – Konrad Rudolph Mar 26 '19 at 12:05
  • This doesn't iterate through them in order - aren't most binary trees BSTs? – Alexander Mills Dec 17 '22 at 05:29
  • @AlexanderMills Yes, the iteration order was already addressed in previous comments. As for whether most binary trees are BSTs — maybe? I haven’t counted. Non-search applications for binary trees definitely exist. – Konrad Rudolph Dec 17 '22 at 14:22
3

Sure, you have two general algorithms, depth first search and breadth first search.

If order of traversal is not important to you, go for breadth first, it's easier to implement for iteration. You're algorithm should look something like this.

LinkedList queue = new LinkedList();

queue.add(root);

while (!queue.isEmpty()){
    Object element = queue.remove();

    queue.add(element.left);
    queue.add(element.right);

    // Do your processing with element;
}
Ivan
  • 1,735
  • 1
  • 17
  • 26
1

As with every recursion, you can use additional data structure - i.e. the stack. A sketch of the solution:

private static void visitall(BinaryTree foo) {
  Stack<BinaryTree> iterationStack = new Stack<BinaryTree>();
  iterationStack.push(foo);

  while (!iterationStack.isEmpty()) {
      BinaryTree current = iterationStack.pop();
      System.out.println(current.node);
      current.push(current.right);        // NOTE! The right one comes first
      current.push(current.left);
   }

}
Grzegorz Oledzki
  • 23,614
  • 16
  • 68
  • 106
  • 1
    Hmmm... To preserve the same order of visiting? If you pushed the left one first and only then the right one, you would pop the right one first and then the left one. LIFO, right? – Grzegorz Oledzki May 31 '10 at 09:58
1

I had a tree (not binary) and eventually solved it with this very simple algorithm. The other solutions used left and right that were not relevant or even implemented in the examples.

My structure was: nodes with each parent containing list of children, and each child containing a pointer back to the parent. Pretty common...

After a bunch of refactoring, I came up with the following example using Kotlin. It should be trivial to convert to your language of choice.

Helper Functions

First, the node must provide 2 simple functions. This will vary depending on your Node class' implementation:

leftMost - This is the first child node. If that node has children, it's first child, etc. If no children, return this.

fun leftMost(): Node {
        if (children.isEmpty()) {
            return this
        }
        var n = this
        while (n.children.isNotEmpty()) {
            n = n.children[0]
        }
        return n
}

nextSibling - The next sibling of this node, or NULL

fun nextSibling(): Node? {
    if (parent == null) return null
    val siblingIndex = parent.children.indexOf(this) + 1
    return if (siblingIndex < parent.children.size) {
        parent.children[siblingIndex]
    } else {
        null
    }
}

The Iteration

The iteration starts with the leftMost of the root.

Then inspect the next sibling.

  • If NOT NULL the sibling's leftMostChild
  • If NULL, the parent, and if the parent is NULL, we are done.

That's it.

Here is a Kotlin iterator function.

fun iterator(): Iterator<Node> {
    var next: Node? = this.leftMost()

    return object : Iterator<Node> {

        override fun hasNext(): Boolean {
            return next != null
        }

        override fun next(): Node {
            val ret = next ?: throw NoSuchElementException()
            next = ret.nextSibling()?.leftMost() ?: ret.parent
            return ret
        }
    }
}

Here is the same next() function, but without the Kotlin shorthand for dealing with NULL values, for those that are not hip to the syntax.

fun next(): Node {
    val ret = next
    if (ret == null) throw NoSuchElementException()
    val nextSibling = ret.nextSibling()
    if (nextSibling != null) {
        next = nextSibling.leftMost()
    }
    else {
        next = ret.parent
    }
    return ret
}
Steven Spungin
  • 27,002
  • 5
  • 88
  • 78
0

Yes, you can change it to iteration instead of a recursion, but then it gets much more complicated, since you need to have some way to remember where to go back from the current node. In the recursive case, the Java call stack handles that, but in an iterative solution you need to build your own stack, or perhaps store back pointers in the nodes.

Thomas Padron-McCarthy
  • 27,232
  • 8
  • 51
  • 75