Logo

Programming-Idioms

  • Java
  • Python
  • Go
  • Rust

Idiom #16 Depth-first traversal of a binary tree

Call a function f on every node of binary tree bt, in depth-first infix order

void dfs(BinTree bt) {
	if (bt.left != null) {
		dfs(bt.left);
        }
	f(bt);
	if (bt.right != null) {
		dfs(bt.right);
        }
}

Here, the call to f is hard-coded, we can't specify f as a parameter.
import java.util.function.Consumer;
class BinTree<T> {
	// ...

	void dfs(Consumer<BinTree<T>> f) {
		if( left != null )
			left.dfs(f);
		f.accept(this);
		if( right != null )
			right.dfs(f);
	}
}

This works in java >= 8.

dfs is a method.

f is functional argument.
class BinTree {
	// ...

	void dfs() {
		if( left != null )
			left.dfs();
		f(this);
		if( right != null )
			right.dfs();
	}
}

dfs is a method.

Here, the call to f is hard-coded, we can't specify f as a parameter.
def dfs(bt):
	if bt is None:
		return
	dfs(bt.left)
	f(bt)
	dfs(bt.right)

Recursive DFS.
func (bt *BinTree[L]) Dfs(f func(*BinTree[L])) {
	if bt == nil {
		return
	}
	bt.Left.Dfs(f)
	f(bt)
	bt.Right.Dfs(f)
}

The type parameter L is for arbitrary node label data
func (bt *BinTree) Dfs(f func(*BinTree)) {
	if bt == nil {
		return
	}
	bt.Left.Dfs(f)
	f(bt)
	bt.Right.Dfs(f)
}

The function f is a parameter of the traversal method Dfs.
It's legit to call a method on a nil receiver, and useful to make code more concise with less checks for nil.
struct BiTree<T> {
    left: Option<Box<BiTree<T>>>,
    right: Option<Box<BiTree<T>>>,
    value: T,
}
fn depthFirstTraverse<T>(bt: &mut BiTree<T>, f: fn(&mut BiTree<T>)) {
    if let Some(left) = &mut bt.left {
        depthFirstTraverse(left, f);
    }
    
    f(bt);
    
    if let Some(right) = &mut bt.right {
        depthFirstTraverse(right, f);
    }
}
void Dfs(BinaryTree bt)
{
    if (bt.Left != null) 
        Dfs(bt.Left);

    f(bt);

    if (bt.right != null) 
        Dfs(bt.Right);
}

New implementation...