Binary Tree Traversal
A binary tree: degrees of all nodes are \(<=2\).
Say the leaf nodes have 2 null children, then each non-null node has one left and one right child (that can be null).
Traversals
X: current node
Traversal |
Order |
|---|---|
Pre-order |
X L R |
In-order |
L X R |
Post-order |
L R X |
The recursive way is obvious but risks of using many stacks.
Node
Define a node as:
struct Node {
Node* lc;
Node* rc;
Node* parent; // some implementations do not have
};
Successor
A successor is the next node to visit in the in-order traversal of current node.
Below is a function finding a successor. It requires a “parent” pointer.
Node* successorOf(Node* n){
if(!n) return nullptr;
if(n->rc) { // leftmost child of rc
n = n->rc; while(n->lc) n = n->lc;
} else { // parent of the ancestor that has n in its right sub tree
while(n->parent && n->parent->rc && n->parent->rc==n) n = n->parent;
n = n->parent;
}
return n;
}
Pre-Order Traversal
Recursive:
template<typename F>
void preOrderTraverse(Node* x, F& visit) {
if(!x) return;
visit(x);
preOrderTraverse(x->lc, visit);
preOrderTraverse(x->rc, visit);
}
Easy to convert since it looks like two tail recursions.
Iterative way using the “left branch first” strategy:
template<typename F>
void preOrderTraverseIterative(Node* root, F& visit) {
stack<Node*> st;
if(root) st.push(root);
while (!st.empty()) {
auto *x = st.top();
st.pop();
while (x) {
visit(x);
if(x->rc) st.push(x->rc);
x = x->lc;
}
}
}
Optimization: for a normal tree, the number of null nodes is much smaller than the number of the other nodes; checking null each time may be wasteful in some cases.
Remove null check (not always better):
template<typename F>
void preOrderTraverseIterative(Node* root, F& visit) {
stack<Node*> st;
st.push(root);
while (!st.empty()) {
auto *x = st.top();
st.pop();
while (x) {
visit(x);
st.push(x->rc);
x = x->lc;
}
}
}
In-Order Traversal
Recursive:
template<typename F>
void inOrderTraverse(Node* x, F& visit) {
if(!x) return;
inOrderTraverse(x->lc, visit);
visit(x);
inOrderTraverse(x->rc, visit);
}
Traversal for right subtree is tail recursive but the one for left is not.
We can apply the similar strategy:
template<typename F>
void inOrderTraverseIterative(Node* x, F& visit) {
stack<Node*> st;
while(x || !st.empty()) {
if (x) {
st.push(x);
x = x->lc;
} else { // x is null and stack is non-empty
x = st.top();
st.pop();
visit(x);
x = x->rc;
}
}
}
Another way using successor function above:
template<typename F>
void inOrderTraverseIterative(Node* x, F& visit) {
while(1) {
if(x->lc) {
x=x->lc;
} else {
visit(x);
while(!x->rc) {
if(!(x = successorOf(x))) return;
visit(x);
}
x=x->rc;
}
}
}
Post-Order Traversal
Recursive:
template<typename F>
void postOrderTraverse(Node* x, F& visit) {
if(!x) return;
postOrderTraverse(x->lc, visit);
postOrderTraverse(x->rc, visit);
visit(x);
}
Hard to convert directly since both are not tail recursions.
One iterative way using the “furthest reachable left leaf” strategy:
template<typename F>
void postOrderTraverseIterative(Node* x, F& visit) {
stack<Node*> st;
if(x) st.push(x);
while(!st.empty()) {
if(x->parent != st.top()) {
while(auto y = st.top()) {
if(y->lc) {
if(y->rc) st.push(y->rc);
st.push(y->lc);
} else {
st.push(y->rc);
}
}
st.pop();
}
x = st.top();
st.pop();
visit(x);
}
}
Read More
Post order traversal of binary tree without recursion - Stack Overflow