This is a companion piece to an article I did a few years ago called `On Binary Tree Traversal`

in which I coded example of `preorder`

, `inorder`

and `postorder`

depth-first search binary tree traversal algorithms in Python, both iteratively and recursively.

In this follow-up, I’d like to do the same in Go and add a few more traversal algorithms.

- Introduction
- Tree Traversal Properites
- Queue Implementation
- Binary Tree Traversal
- Common Algorithms
- References

# Introduction

One of the reasons why I wanted to revisit tree traversal algorithms is because I understand them better now and can explicate them better than I could before.

For example, everybody regurgitates the same things about tree strategies, such as, “use a stack for depth-first search” and “use a queue for breadth-first search”, and not much more information is offered.

Now, this isn’t wrong, but it’s rote. We should all strive to do better. Let’s begin now.

## Stack

Why is a stack a preferred data structure for depth-first search (`DFS`

)? It’s generally because the child nodes are to be inspected first in a `DFS`

traversal algorithm, notably `inorder`

and `postorder`

.

Recall that the order in which an element is added to or removed from a stack data structure is **last in, first out** (`LIFO`

).

This is crucial for `DFS`

because the child nodes need to be inspected first, and the way to do this is to push the parent node onto the stack before its child nodes. This guarantees that when the node elements are popped off of the stack that they will be retrieved in the reverse order in which they were pushed onto the stack, thus allowing us to reach the child nodes first.

Let’s try to visualize this. Here is a complete binary search tree (of course, not all binary trees will be complete or will be binary search trees):

10 / \ / \ 7 25 / \ / \ 4 8

When traversing a binary search tree (`BST`

), the order of the visited nodes will depend upon what you’re trying to accomplish. However, notably, and in stark contrast with a breadth-first search, a node will have its direct child nodes traversed *before* its sibling nodes (again, this is most apparent in the `inorder`

and `postorder`

traversals).

So, using the tree above, the nodes will be pushed onto the stack in the following order for a classic `preorder`

`DFS`

traversal:

`push(10)`

-> `push(25)`

-> `push(7)`

-> `push(8)`

-> `push(4)`

If the stack were an array, it would look like this:

`[10, 25, 7, 8, 4]`

Note that this isn’t the final result. Instead, this order guarantees the of the `preorder`

`DFS`

traversal, which is `parent -> left child -> right child`

.

How is that? Again, recall that a tree is made up of a number of smaller subtrees. If we want to access the parent node’s left child *before* the right child, which data structure will allow for it? Think about how the nodes need to be positioned in the stack.

For a **last in, first out** ordered data structure, the left child node **must** be pushed onto the stack **after** the right child node:

`push(10)`

-> `push(25)`

-> `push(7)`

Thinking this through even further, it means that once this particular subtree has been inspected and pushed onto the stack, and more subtrees (for instance, where the left child node of the root node is now the parent node of its **own** child nodes), will be traversed in the same way, guaranteeing that all of the left subtrees will be traversed *before* any of the right subtrees.

The ordering of the elements in the (

`LIFO`

) stack is theoppositeorder of a breadth-first search.

So, when someone says that a stack is the perfect data structure for a `DFS`

, they’re really saying all of the above, too.

Note that recursive functions are perfect for

`DFS`

traversals, because the function call stackisa stack data structure.

## Queue

Right-o. Now, let’s take a look at a **first in, first out** (`FIFO`

) data structure, the queue. This is the go-to data structure for doing a breadth-first search (`BFS`

) traversal.

Now, using the tree above, the nodes will be pushed into the queue in the following order for a classic `level order`

`BFS`

traversal:

`push(10)`

-> `push(7)`

-> `push(25)`

-> `push(4)`

-> `push(8)`

If the queue were an array, it would look like this:

`[10, 7, 25, 4, 8]`

This reads the tree from left to right, level by level. Notably, it is not possible to descended into the children of a node (the subtree) **before** the level is fully traversed. This is why the queue is reached for when implementing a `BFS`

traversal.

So, when someone says that a queue is the perfect data structure for a `BFS`

, they’re really saying all of the above, too.

# Tree Traversal Properites

The most important thing to do when approaching an algorithm is to stop and *think*. Spend time ensuring that you know **exactly** what is being asked of you. Spend time thinking about the inherent qualities of the data structures and the traversal strategies that you are thinking of employing in your solution.

The

whyis often more important than thehow. Indeed, feeling confident in the former is often the key to the latter.

Here are some properties of different tree traversal strategies that are helpful to know:

`preorder`

- begins at the root and ends at the right-most node
- will explore the roots before the leaves
- can be used to create a copy of the tree as it traverses the tree nodes in order of insertion
- in other words, create a copy of the tree by appending the nodes to an array in the order of traversal followed by inserting them into a new tree

`inorder`

- begins at the left-most node and ends at the right-most node
- will get the values of the nodes in increasing order for a
`BST`

`postorder`

- begins at the left-most node and ends at the root
- will explore the leaves before the roots
- can be used to delete an entire tree since it first visits the leaves

# Queue Implementation

The traversals that perform a breadth-first traversal will use the following queue implementation:

```
package queue
type Node struct {
Data any
Next *Node
}
type Queue struct {
First *Node
Last *Node
Length int
}
func New() *Queue {
return &Queue{
First: nil,
Last: nil,
Length: 0,
}
}
func (q *Queue) Dequeue() *Node {
if q.First == nil {
return nil
}
node := q.First
if q.Length == 1 {
q.First = nil
q.Last = nil
} else {
q.First = q.First.Next
node.Next = nil
}
q.Length -= 1
return node
}
func (q *Queue) Enqueue(data any) *Node {
node := &Node{
Data: data,
Next: nil,
}
if q.First == nil {
q.First = node
q.Last = node
} else {
q.Last.Next = node
q.Last = node
}
q.Length += 1
return node
}
func (q *Queue) IsEmpty() bool {
if q.Length == 0 {
return true
}
return false
}
```

# Binary Tree Traversal

All of the examples will use the following types and helper function(s):

```
package tree
type Node struct {
Value int
Left *Node
Right *Node
}
type NodeStack []*Node
type Visited []int
func CreateNode(value int) *Node {
return &Node{
Value: value,
Left: nil,
Right: nil,
}
}
```

Although the examples here are operating on binary

searchtrees, they can be used on all binary trees.

## Depth-First Search

### Preorder

#### Recursive

```
func __preorder(node *Node, visited []int) Visited {
if node == nil {
return visited
}
visited = append(visited, node.Value)
visited = __preorder(node.Left, visited)
visited = __preorder(node.Right, visited)
return visited
}
func Preorder(node *Node) Visited {
return __preorder(node, []int{})
}
```

- Time complexity: O(N)
- Space complexity: O(N)

#### Iterative

```
func Preorder(node *Node) Visited {
if node == nil {
return nil
}
stack := NodeStack{node}
visited := Visited{}
var current *Node
for len(stack) > 0 {
current, stack = stack[len(stack)-1], stack[:len(stack)-1]
visited = append(visited, current.Value)
if current.Right != nil {
stack = append(stack, current.Right)
}
if current.Left != nil {
stack = append(stack, current.Left)
}
}
return visited
}
```

Notes:

- Note that the order that we push nodes onto the stack is critical. The right node must be pushed on before the left node to maintain the correct order, as the left node will then be popped first.

- Time complexity: O(N)
- Space complexity: O(N)

### Inorder

#### Recursive

```
func __inorder(node *Node, visited []int) Visited {
if node == nil {
return visited
}
visited = __inorder(node.Left, visited)
visited = append(visited, node.Value)
visited = __inorder(node.Right, visited)
return visited
}
func Inorder(node *Node) Visited {
return __inorder(node, []int{})
}
```

- Time complexity: O(N)
- Space complexity: O(N)

#### Iterative

```
func Inorder(node *Node) Visited {
if node == nil {
return nil
}
stack := NodeStack{}
visited := Visited{}
current := node
for {
for current != nil {
stack = append(stack, current)
current = current.Left
}
if len(stack) == 0 {
break
}
current, stack = stack[len(stack)-1], stack[:len(stack)-1]
visited = append(visited, current.Value)
current = current.Right
}
return visited
}
```

Notes:

- Unlike other iterative traversals, we don’t start with the root already in the stack.
- Traverse as far left as we can.
- Pop the leftmost node.
- Assume there’s a right node. If not, the next left node will be popped, and then we’ll again assume there’s a right node.

- Time complexity: O(N)
- Space complexity: O(N)

### Postorder

#### Recursive

```
func __postorder(node *Node, visited []int) Visited {
if node == nil {
return visited
}
visited = __postorder(node.Left, visited)
visited = __postorder(node.Right, visited)
visited = append(visited, node.Value)
return visited
}
func Postorder(node *Node) Visited {
return __postorder(node, []int{})
}
```

- Time complexity: O(N)
- Space complexity: O(N)

#### Iterative

```
func Postorder(node *Node) Visited {
if node == nil {
return nil
}
stack := NodeStack{node}
q := queue.New()
var current *Node
for len(stack) > 0 {
current, stack = stack[len(stack)-1], stack[:len(stack)-1]
q.Enqueue(current.Value)
if current.Left != nil {
stack = append(stack, current.Left)
}
if current.Right != nil {
stack = append(stack, current.Right)
}
}
return q.ToArray()
}
```

Notes:

- This algorithm, unlike the others, uses both a stack and a queue.

- Time complexity: O(N)
- Space complexity: O(N)

## Breadth-First Search

```
func Bfs(node *Node) Visited {
if node == nil {
return nil
}
q := queue.New()
q.Enqueue(node)
visited := Visited{}
var current *Node
for !q.IsEmpty() {
current = q.Dequeue().Data.(*Node)
visited = append(visited, current.Value)
if current.Left != nil {
q.Enqueue(current.Left)
}
if current.Right != nil {
q.Enqueue(current.Right)
}
}
return visited
}
```

### Level Order

#### Recursive

```
func __levelorder(node *Node, levels [][]int, level int) [][]int {
if node == nil {
return levels
}
levels[level] = append(levels[level], node.Value)
levels = __levelorder(node.Left, levels, level+1)
levels = __levelorder(node.Right, levels, level+1)
return levels
}
func Levelorder(node *Node) [][]int {
return __levelorder(node, [][]int{}, 0)
}
```

#### Iterative

```
func Levelorder(node *Node) []Visited {
if node == nil {
return nil
}
q := queue.New()
q.Enqueue(node)
levels := []Visited{}
var current *Node
for !q.IsEmpty() {
size := q.Length
level := Visited{}
for i := 0; i < size; i += 1 {
current = q.Dequeue().Data.(*Node)
level = append(level, current.Value)
if current.Left != nil {
q.Enqueue(current.Left)
}
if current.Right != nil {
q.Enqueue(current.Right)
}
}
levels = append(levels, level)
}
return levels
}
```

The important part of this is the inner loop that iterates through the *current* size of the queue. That is, anything pushed into the queue within the inner loop will **not** influence the size, since it’s been saved to a local variable.

If the size variable was dynamic, for instance `g.Length`

, then this approach would not work, as the algorithm would not be able to differentiate between pushing nodes into the queue that were direct children of the `current`

node and those that were grandchildren and great-grandchildren, etc. (so to speak).

By limiting the size to that of the queue before the inner loop is (again) iterated, we’re guaranteeing that those nodes “collected” during the subsequent loop will **only** be those that are direct children.

This, of course, is what is necessary to collect for *level* order traversal.

An interesting trait of this algorithm is that the queue, prior to the inner loop, will be logarithmic. For instance:

Level |
Maximum Number Of Direct Child Nodes |
---|---|

0 | 1 (root node) |

1 (root node) | 2 |

2 | 4 |

3 | 8 |

4 | 16 |

5 | 32 |

6 | 64 |

Recall that the maximum number of nodes in a

perfecttree can be calculated by the formula`2^k - 1`

(that’s for the entire tree, not by level).

Just as the `level`

variable in the recursive version is acting as a lookup for the particular level that is being executed on the stack (i.e, the stack frame), calculating the length of the `levels`

list of lists in the iterative version can be used to determine what level of the tree is currently being enumerated.

Also notably, the number of nodes can never exceed the logarithm of the current level. For example, if the current level is four, the number of direct child nodes that can be pushed into the queue can never exceed 16.

# Common Algorithms

Note that I’m not using Go’s

`math.Max`

method because it returns a`float64`

, and I want to consistently return an`int`

.

## Tree Height

### Recursive

```
func Height(node *tree.Node) int {
if node == nil {
return 0
}
left := Height(node.Left) + 1
right := Height(node.Right) + 1
if left > right {
return left
}
return right
}
```

### Iterative

Here is an implementation using a stack. It uses the classic depth-first search `preorder`

traversal:

```
type N struct {
Node *tree.Node
Height int
}
func IterativeHeight(node *tree.Node) int {
if node == nil {
return 0
}
stack := []*N{&N{
Node: node,
Height: 1,
}}
max := -(1 << 63)
var current *N
for len(stack) > 0 {
current, stack = stack[len(stack)-1], stack[:len(stack)-1]
if max < current.Height {
max = current.Height
}
node = current.Node
if node.Right != nil {
stack = append(stack, &N{
Node: node.Right,
Height: current.Height + 1,
})
}
if node.Left != nil {
stack = append(stack, &N{
Node: node.Left,
Height: current.Height + 1,
})
}
}
return max
}
```

And, here is an implementation using a queue. Note that it uses the iterative level order traversal idiom:

```
func IterativeHeight(node *tree.Node) int {
if node == nil {
return 0
}
q := queue.New()
q.Enqueue(node)
max := -(1 << 63)
height := 0
var current *tree.Node
for !q.IsEmpty() {
size := q.Length
height += 1
for i := 0; i < size; i += 1 {
current = q.Dequeue().Data.(*tree.Node)
if current.Left != nil {
q.Enqueue(current.Left)
}
if current.Right != nil {
q.Enqueue(current.Right)
}
}
if max < height {
max = height
}
}
return max
}
```

## Validate Binary Search Tree

### Recursive

```
var min *int
var max *int
func _validate(node *tree.Node, min, max *int) bool {
if node == nil {
return true
}
if (min != nil && node.Value <= *min) || (max != nil && node.Value > *max) {
return false
}
if !_validate(node.Left, min, &node.Value) || !_validate(node.Right, &node.Value, max) {
return false
}
return true
}
func ValidateBST(nums []int) bool {
t := tree.CreateBST(nums)
return _validate(t.Root, min, max)
}
```

### Iterative

This implementation does a depth-first `inorder`

traversal, which will return the values, well, in order. Simply ensuring the current value is larger than the last is enough to validate the binary search tree.

```
func ValidateBST(node *tree.Node) bool {
if node == nil {
return false
}
stack := tree.NodeStack{}
current := node
lastNodeValue := -(1 << 63)
for {
for current != nil {
stack = append(stack, current)
current = current.Left
}
if len(stack) == 0 {
return true
}
current, stack = stack[len(stack)-1], stack[:len(stack)-1]
if current.Value < lastNodeValue {
return false
}
lastNodeValue = current.Value
current = current.Right
}
}
```