How to Calculate a Binary Tree‘s Height Using Array Iteration in Ruby

Binary trees are a fundamental data structure in computer science, used to efficiently store and organize hierarchical data. Understanding how to work with binary trees is an essential skill for aspiring software engineers and programmers.

In this article, we‘ll take an in-depth look at binary trees and learn how to calculate the height of a binary tree represented as an array using iteration in Ruby. While recursive solutions tend to be more concise, the iterative approach avoids potential stack overflow errors and can be more efficient for larger trees.

Binary Tree Basics

Before we dive into the height calculation, let‘s review some key terminology and properties of binary trees.

A binary tree is a tree data structure composed of nodes, where each node has at most two child nodes, referred to as its left and right children. The topmost node is called the root. Nodes with no children are known as leaf nodes or leaves.

Here are a few other important terms to know:

Parent – The node directly above a node is considered that node‘s parent. Every node except the root has exactly one parent.

Sibling – Nodes that share the same parent are siblings.

Level – The level of a node refers to its distance from the root. The root is at level 0, its children are level 1, their children are level 2, and so on.

Height – The height of a binary tree is the number of edges on the longest downward path between the root and a leaf. A tree with just one node (the root) has a height of 0.

Perfect Binary Tree – A perfect binary tree is one where every level except the last is completely filled, and all nodes in the last level are as far left as possible. Perfect binary trees have 2^(h+1) – 1 nodes, where h is the height of the tree.

Here‘s an example of a simple binary tree:

     4
   /   \
  2     7
 / \   / \
1   3 6   9

In this tree, the root is the node with value 4. It has a left child (2) and a right child (7). Nodes 1, 3, 6 and 9 are leaves. The height of this tree is 2, as the longest path from root to leaf has 2 edges (4 -> 2 -> 1 or 4 -> 7 -> 9).

Array Representation of Binary Trees

Binary trees can be represented in multiple ways, but a common approach is to use an array. In this representation, the elements are stored level-by-level from left to right.

For a node at index i, its children are found at the following indices:

  • Left child: 2i + 1
  • Right child: 2i + 2

Conversely, for a node at index i, its parent is located at index floor((i-1)/2).

Here‘s how the example binary tree from earlier would look as an array:

[4, 2, 7, 1, 3, 6, 9]

The root 4 is at index 0. Its left child 2 is at index 1 (20+1) and right child 7 at index 2 (20+2). 2‘s children 1 and 3 are at indices 3 (21+1) and 4 (21+2) respectively.

Representing binary trees as arrays allows us to efficiently access and manipulate elements without needing to define a node class. However, this flat structure obscures the hierarchical nature of the tree.

Calculating Binary Tree Height Iteratively

Now that we understand how binary trees work and how to store them as arrays, let‘s look at how to determine the height of the tree using iteration.

We‘ll break this down step-by-step. Here‘s the complete code, which we‘ll explain in detail:

def binary_tree_height(tree)
  return 0 if tree.empty?

  height = 0
  nodes = 1

  while nodes > 0
    height += 1
    level_nodes = 0

    nodes.times do |i|
      left_index = 2 * i + 1
      right_index = 2 * i + 2

      level_nodes += 1 if left_index < tree.length
      level_nodes += 1 if right_index < tree.length
    end

    nodes = level_nodes
  end

  height
end

Let‘s step through it:

  1. First we handle the edge case of an empty tree, which by definition has a height of 0. We can just return 0 immediately.

  2. We initialize two variables:

    • height to keep track of the current height
    • nodes to store the number of nodes at the current level
  3. We begin a while loop that will continue processing levels of the tree until there are no more nodes left to check (i.e. we‘ve reached the bottom). Each iteration represents stepping down one level in the tree.

  4. At the start of each loop, we increment height by 1 since we‘re moving to the next level. We also initialize level_nodes to 0, which will count the number of child nodes found on this level.

  5. We begin a nested iteration using times to check each node at the current level. The number of iterations is determined by nodes, which represents the number of nodes at this level.

  6. For each node, we calculate the indices of its potential left and right children using the formulas 2 * i + 1 and 2 * i + 2 respectively, where i is the node‘s index.

  7. If the calculated index for a left child is less than the length of the tree array, that means this node has a left child. We increment level_nodes by 1 to count it. We do the same check for a right child.

  8. After we‘ve checked all nodes at this level, we update nodes to be the value of level_nodes. This sets us up to process the next level on the subsequent iteration of the outer loop.

  9. We continue this process, incrementing height and checking for child nodes level by level, until we reach a level where no nodes have children. At that point, nodes will be 0 and the while loop will exit.

  10. Finally, we implicitly return the value of height, which now represents the total height of the tree.

Here‘s an example of this in action:

tree = [5, 3, 8, 1, 4, 7, 9]
puts binary_tree_height(tree)  # Output: 2

For this tree, the algorithm would:

  • Initialize height to 0 and nodes to 1
  • Check the root 5 (the only node on level 0), find that it has 2 children, so set level_nodes to 2
  • Increment height to 1, update nodes to 2, check 3 and 8 for children
  • 3 has children 1 and 4, 8 has children 7 and 9, so set level_nodes to 4
  • Increment height to 2, update nodes to 4, check 1, 4, 7, 9 for children
  • No children found, so level_nodes stays 0, exit loop and return height of 2

Time and Space Complexity Analysis

Let‘s analyze the time and space complexity of this iterative algorithm.

For time complexity, the outer while loop runs once for each level of the tree, which is bounded by the height h. For a perfect binary tree, the height is log(n), where n is the number of nodes.

The inner times loop runs once for each node on the current level. In the worst case, this will be n/2 iterations for the bottom level of a perfect binary tree.

So in the worst case, the total number of iterations is:

1 + 2 + 4 + … + n/2 = n – 1

Therefore, the time complexity is O(n), where n is the number of nodes in the tree. This means the algorithm scales linearly with the size of the tree.

As for space complexity, this algorithm only uses a constant amount of extra space for the height, nodes and level_nodes variables, regardless of the size of the input. Thus the space complexity is O(1).

Comparison to Recursive Approach

It‘s worth briefly comparing this iterative approach to a recursive one. A recursive solution might look like:

def binary_tree_height_recursive(tree, index = 0)
  return 0 if index >= tree.length || tree[index].nil?

  left_height = binary_tree_height_recursive(tree, 2 * index + 1) 
  right_height = binary_tree_height_recursive(tree, 2 * index + 2)

  [left_height, right_height].max + 1
end

The recursive approach is more concise and perhaps more intuitive, as it mirrors the hierarchical structure of the tree. However, it has the potential to cause stack overflow errors for very deep trees due to the recursive calls.

The iterative approach avoids this issue by using a loop instead of recursion. It also has the same time complexity of O(n), although the constant factors may be slightly larger due to the nested loop.

In practice, the recursive solution is often preferred for its simplicity. But it‘s important to understand both approaches and their tradeoffs.

Conclusion

In this article, we‘ve taken a comprehensive look at binary trees and how to calculate their height using array iteration in Ruby. We discussed:

  • The key properties and terminology of binary trees
  • How to represent binary trees using arrays
  • A detailed breakdown of an iterative algorithm to calculate tree height
  • Analysis of the time and space complexity of this approach
  • Comparison to a recursive solution

Mastering these fundamentals is crucial for any programmer looking to build efficient, scalable software. I hope this has been a helpful introduction to working with binary trees in Ruby.

Remember, the best way to truly understand these concepts is through practice. Try implementing this algorithm yourself and testing it on various example trees. Experiment with the recursive approach as well and see if you can understand how it works.

Happy coding!

Similar Posts