Table of Contents
The recursive traversal algorithms work well for implementing
tree-based ADT member functions, but if we are trying to hide the trees
inside some ADT (e.g.,
std::set), we may need to provide
iterators for walking though the contents of the tree.
Iterators for tree-based data structures can be more complicated than those for linear structures.
For arrays (and vectors and deques and other array-like structures) and linked lists, a single pointer can implement an iterator:
Given the current position, it is easy to move forward to the next element.
For anything but a singly-linked list, we can also easily move backwards.
But look at this tree, and suppose that you were implementing tree iterators as a single pointer. Let's see if we can "think" our way through the process of traversing this tree, one step at a time, without needing to keep a whole stack of unfinished recursive calls around.
It's not immediately obvious what our data structure for storing our "current position" (i.e., an iterator) will be. We might suspect that a pointer to a tree node will be part or whole of that data structure, in only because that worked for us with iterators over linked lists. With that in mind, ...
Question: How would you implement
We find the
begin() position by starting from the
root and working our way down, always taking left children, until we
come to a node with no left child.
Probably by returning a null pointer.
It's tempting to guess that you could do much the same as for
begin(), this time seeking out the rightmost node. But that
would leave you pointing to the last node in the
end() is supposed to denote the position
after the last node in the tree.
Now it get's trickier. Suppose you are still trying to implement
iterators using a single pointer, you have one such pointer named
current as shown in the figure.
Question: How would you implement
You can't, not with just a pointer to the node and all the nodes pointing only to their children.
In a binary tree, to do operator
We need to know not only where we are,
but also how we got here.
One way is to do that is to implement the iterator as a stack of pointers containing the path to the current node. In essence, we would use the stack to simulate the activation stack during a recursive traversal. but that's pretty clumsy. Iterators tend to get assigned (copied) a lot, and we'd really like that to be an O(1) operation, having to copy an entire stack of pointers just isn't very attractive.
A simpler approach is to add pointers from each node to its
These nodes are then used to to implement a tree class, which keeps track of the root of our tree in a data member.
Here's the basic declaration for an iterator to do in-order traversals.
You will note that the code shown here is pretty much a standard
iterator implemented by a standard pointer. There's really not much here
to distinguish this from a
As noted earlier,
begin() works by finding the
leftmost node in the tree, and
end() uses a null pointer
(there is an implicit type conversion in the
converting a pointer to an iterator by implicitly calling the
inorderTreeIterator constructor that takes a single pointer
as its argument.
Before trying to write the code for this iterator's
operator++, let's try to figure out just what it should
Question: Suppose that we are currently at node E. What is the in-order successor (the node that comes next during an in-order traversal) of E?
G is the in-order successor of E. (If you answered F, remember that in an in-order traversal, we visit a node only after visiting all of its left descendents and before visiting any of its right descendents. Since we're at E, we must have already visited F.)
The previous example suggests that a node's in-order successor tends to be among its right descendents.
Let's explore that idea further.
Question: Suppose that we are currently at node A. What is the in-order successor (the node that comes next during an in-order traversal) of A?
F is the in-order successor of A.
If we are at A during an in-order traversal, we have already visited all of A's left descendents. So the answer has to be C or one of its descendents. It's tempting to pick C because it's only one step away from A. But, remember, during an in-order traversal, we visit a node only after visiting all of its left descendents and before visiting any of its right descendents. We have not yet visited C's left descendents. So have to run down from C to the left as far as we can go.
This suggests that, if a node has any right descendents, we should
Take a step down to the right, then
Run as far down to the left as we can.
You can see how this would take us from A to F.
But that "step to the right, then run left" procedure raises a new question. What happens if we are at a node with no right descendents?
Question: Suppose that we are currently at node C. What is the in-order successor of C?
C does not have an in-order successor. C is
actually the final node in an in-order traversal. After C is only
OK, that's an interesting special case, but it doesn't make clear what should happen in the more general case where we have no right child.
Question: What is the in-order successor of F?
E is the in-order successor of F.
So, when we have no right child, we may need to move back up in the tree.
Question: What is the in-order successor of G?
C is the in-order successor of G.
Why did we move up two steps in the tree this time, when from F we only moved up one step? The answer lies in whether we moved back up over a left-child edge or a right-child edge.
If we move up over a right-child edge, we're returning to a node that has already had all of its descendents, left and right, visited. So we must have already visited this node as well, otherwise we would never have made it into its right descendents.
If we move up over a left-child edge, we're returning to a node that has already had all of its left descendents visited but none of its right descendents. That's the definition of when we want to visit a node during an in-order traversal, so it's time to visit this node.
So, if a node has no right child, we move up in the tree
parent pointers) until we move back over a
left edge. Then we stop.
Notice that, applying this procedure to C, we would move up to A (right edge), then try to move up again to A's parent. But since A is the tree root, it's parent pointer will be null, which is our signal that C has no in-order successor.
If the current node has a non-null right child,
Take a step down to the right
Then run down to the left as far as possible
If the current node has a null right child,
move up the tree until we have moved over a left child link
With that in mind, the
operator++ code should be
You can run these iterator algorithms here.
A similar process of analysis would eventually lead us to an implementation of operator--.