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:
-
First we handle the edge case of an empty tree, which by definition has a height of 0. We can just return 0 immediately.
-
We initialize two variables:
height
to keep track of the current heightnodes
to store the number of nodes at the current level
-
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. -
At the start of each loop, we increment
height
by 1 since we‘re moving to the next level. We also initializelevel_nodes
to 0, which will count the number of child nodes found on this level. -
We begin a nested iteration using
times
to check each node at the current level. The number of iterations is determined bynodes
, which represents the number of nodes at this level. -
For each node, we calculate the indices of its potential left and right children using the formulas
2 * i + 1
and2 * i + 2
respectively, wherei
is the node‘s index. -
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 incrementlevel_nodes
by 1 to count it. We do the same check for a right child. -
After we‘ve checked all nodes at this level, we update
nodes
to be the value oflevel_nodes
. This sets us up to process the next level on the subsequent iteration of the outer loop. -
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 thewhile
loop will exit. -
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 andnodes
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, updatenodes
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, updatenodes
to 4, check 1, 4, 7, 9 for children - No children found, so
level_nodes
stays 0, exit loop and returnheight
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!