Nilesh Sinha

Bottom View of Binary Tree

Problem Statement

We need to print the bottom view of binary tree, i.e., all the bottommost nodes from left to right in a binary tree.

The 3D projection of a binary tree will have different views from different angles. In our case, we’ll print the bottom view of the binary tree.

It’s better to visualize them with horizontal distance (like an x-axis). The root node will be located at the horizontal distance or hd 0. The left child will have hd-1 of its parent and the child will have hd+1 of its parent. The bottommost nodes at each horizontal distance will be present in our output.

Note: Let’s also assume that each child node makes a 45-degree angle with its parent node.

Let’s look at this simple example of what the bottom view really looks like.

bottom-view-of-binary-tree

The bottom view of this binary tree will be 7 9 13 11 6.

Example

Note: To avoid confusion, level and height are the same thing in our topic of discussion.

Now, we’ll again assume the same above example because we haven’t discussed the height which will be used in our algorithm.

binary-tree-height

In this case, we’ll get the output as 7 9 13 11 6.

But how about a different scenario. What if there are two child nodes at the same level and horizontal distance?

two-child-nodes-at-same-level

Example Explanation

Just like the x-axis, the root node is present at the origin (with horizontal distance, say hd, being 0) and at height 0. The immediate child nodes of the root node will be at height 1 with the left child node having a horizontal distance of hd-1 and the right child node having a horizontal distance of hd+1.

And if there are two nodes present at the same height as well as horizontal distance, the bottom-most node will be the one that comes later while traversing from left to right.

Constraints

Although we are not making any assumptions for the input nodes because they are hard-coded, let us assume that:

-10^9 <= node's value <= 10^9

Approach 1 – HashMap & Recursion

We discussed how horizontal distance as well as levels in the above example. What if we start traversing the parent nopde and then its left and right child nodes one by one. As we go down at each of the horizontal distance axis vertically, we’ll keep on updating our bottommost nodes. In this case, we’ll start with the parent node (which has horizontal distance, hd, 0) and then traverse through its left and right child nodes. Since we want only the bottom-most nodes at each horizontal distance, we’ll traverse through every node until we reach the maximum height where we’ll have all the bottommost nodes of the tree.

two-child-nodes-at-same-level2

Technically, we’ll need a TreeMap that stores and sorts the entries based on their keys.

At height 0 and hd 0, we’ll store the parent node in the map with its hd being the key and an array as the value that contains both height and data of the node. We’ll traverse through its left and right child nodes and update the TreeMap if there’s already a key present in the map. It recursively goes on until

we traverse each node in the tree. 

treemap

Algorithm

  • Create a hashmap/treemap that stores the horizontal distance as the key and an array containing the node’s value and its height as the value of the map.
  • We’ll perform the preorder traversal on the binary tree. (We need to traverse the parent node before traversing its left and right child nodes. This can be performed with preorder traversal).
  • If we reach a node having the horizontal distance that already exists as key in the map and a height greater than that of the previous node, we need to update this key with the new node and its height. 
  • Else, we’ll put a new key (horizontal distance) with the current node’s value and its height.

Eventually, we’ll end up getting the bottom-most nodes of the binary tree.

Code Implementation

C++

#include <bits/stdc++.h>
#include <map>
using namespace std;

//Create new nodes with the class Node
struct Node{
    /* class Node contains:
         ->node's value
         ->horizontal distance (hd)
         ->left and right child node
    */
	int val; //node's value
	int hd; //horizontal distance 
	
	// Left and right child nodes
	Node * left;
	Node * right;
	
	//Constructor
	Node(int data){
		val = data;
		hd = INT_MAX;//hd will be updated later
		left = NULL;
		right = NULL;
	}
};

void bottomViewHelper(Node * node, int height, int hd, map <int, pair <int, int>> & map){
	//A base case that returns when there's no node
	if (node == NULL)
		return;
	
	//If any node for the current horizontal distance already exists, and
    // the current node's height is greater than that of previous one 
    //we'll update it with the current node's value and height
	if (map.find(hd) == map.end()){
		map[hd] = make_pair(node -> val, height);
	}
	else{
	    //when the horizontal distance is not present in the map,
        //we'll simply add the current one
		pair < int, int > p = map[hd];
		
		//if height is greater
		if (p.second <= height){
		    map[hd].first = node -> val; //putting new node's value
			map[hd].second = height; //updating height
		}
	}
	
	//Recursively call left child node
	bottomViewHelper(node -> left, height + 1, hd - 1, map);
	
	//Recursively call right child node
	bottomViewHelper(node -> right, height + 1, hd + 1, map);
}

void bottomView(Node * root){
    //Key -> horizontal distance,
    //value -> {node's value, height}
	map < int, pair < int, int > > m;
	
	//Calling helper method
	bottomViewHelper(root, 0, 0, m);
	
	//Printing the bottom-most nodes of the binary tree
	map < int, pair < int, int > > ::iterator iter;
	
	for (iter = m.begin(); iter != m.end(); ++iter){
		pair < int, int > p = iter -> second;
		cout << p.first << " ";
	}
}

int main(){
    //Create a binary tree
	Node * root = new Node(3);
	
	root -> left = new Node(6);
	root -> right = new Node(12);
	
	root -> left -> left = new Node(5);
	root -> left -> right = new Node(11);
	root -> right -> left = new Node(7);
	root -> right -> right = new Node(8);
	
	
	root -> left -> right -> left = new Node(20);
	root -> left -> right -> right = new Node(17);
	cout << "The bottom view of the binary tree is: ";
	
	bottomView(root);
	return 0;
}

Java

import java.util.*;

class Main{

    //Create new nodes with the class Node
    static class Node
    {
        /* class Node contains:
         ->node's value
         ->horizontal distance (hd)
         ->left and right child node
        */


        int val; //node's value

        int hd;  //horizontal distance 

        // Left and right child nodes
        Node left;
        Node right;

        //Constructor 
        public Node(int val)
        {
            this.val = val;
            this.hd = Integer.MAX_VALUE; //hd will be updated later
            this.left = null;
            this.right = null;
        }
    }

    static void bottomViewHelper(Node node, int height, int hd, TreeMap<Integer, int[]> map)
    {
        //A base case that returns when there's no node
        if(node == null) return;

        //If any node for the current horizontal distance already exists, and
        // the current node's height is greater than that of previous one 
        //we'll update it with the current node's value and height
        
        if(map.containsKey(hd) == true)
        {
            int[] existing = map.get(hd);

            //if height is greater
            if(existing[1] <= height)
            {
                existing[0] = node.val; //putting new node's value
                existing[1] = height; //updating height
            }

            map.put(hd, existing); //update and add to the map
        }else
        {
            //when the horizontal distance is not present in the map,
            //we'll simply add the current one
            map.put(hd, new int[]{node.val, height}); //add to the map
        }

        //Recursively call left child node
        bottomViewHelper(node.left,  height+1, hd-1, map);

        //Recursively call right child node
        bottomViewHelper(node.right, height+1, hd+1, map);
    }

    static void bottomView(Node root)
    {
        //TreeMap
        //Key -> horizontal distance,
        //value -> {node's value, height}
        TreeMap<Integer, int[]> map = new TreeMap<>();

        //Calling helper method
        bottomViewHelper(root, 0, 0, map);


        //Printing the bottom-most nodes of the binary tree
        for(int arrays[]: map.values())
        {
            System.out.print(arrays[0] + " ");
        }

    }
    public static void main(String[] args)
    {

        //Create a binary tree
        Node root = new Node(3);
        
        root.left = new Node(6);
        root.right = new Node(12);
        
        root.left.left = new Node(5);
        root.left.right = new Node(11);
        root.right.left = new Node(7);
        root.right.right = new Node(8);
        
        root.left.right.left = new Node(20);
        root.left.right.right = new Node(17);
 
        System.out.print("The bottom view of the binary tree is: ");
 
        bottomView(root);
    }
}

Python

# View of Binary Tree
class Node:
	#class Node contains:
    #->node's value
    #->horizontal distance (hd)
    #->left and right child node
    
	def __init__(self, val = None,
					left = None,
					right = None):
						
		self.data = val  #node's value
		
		# Left and right child nodes
		self.left = left  
		self.right = right
		

def bottomViewHelper(node, d, hd, height):
	
	# A base case that returns when there's no node
	if node is None:
		return
	
	# if horizontal distance is already present in the dictionary, then it should be updated with new node data & height
	if hd in d:
		if height >= d[hd][1]:
			d[hd] = [node.data, height]
	else: # else the new horizontal distance will be inserted in the dictionary with new node data & height
		d[hd] = [node.data, height]
		
	
	bottomViewHelper(node.left, d, hd - 1, height + 1)
	
	
	bottomViewHelper(node.right, d, hd + 1, height + 1)

def bottomView(root):
	
	# Creating a dictionary
	# key -> horizontal distance
	# value -> pair of node's value and height
	d = dict()
	
	bottomViewHelper(root, d, 0, 0)
	
	
	for i in sorted(d.keys()):
		print(d[i][0], end = " ")

if __name__ == '__main__':
	
	# Create a binary tree
	root = Node(3);
			
	root.left = Node(6);
	root.right = Node(12);
			
	root.left.left = Node(5);
	root.left.right = Node(11);
	root.right.left = Node(7);
	root.right.right = Node(8);
			
	root.left.right.left = Node(20);
	root.left.right.right = Node(17);
	
	print("The bottom view of the binary tree is: ", end=" ")
	
	bottomView(root)

Output

The bottom view of the binary tree is: 5 20 7 17 8 

Time Complexity

Since we are visiting each node in the tree once, the time complexity will be: O(N * logN), where N is the number of nodes of the binary tree

Space Complexity

We have used a map in our code to store all the nodes of the tree. If there are N nodes in a tree, the space complexity will be: O(N), where N is the number of nodes

Approach 2 – Queue

Note: We’re calling the horizontal distance as hd in this approach too.

We already know that the sole purpose of a queue is to have First-in-First-out (FIFO) sequence. So, we can insert the parent node first in the queue along with the horizontal distance (hd=0). Following it will be the insertion of its left and right child node with their horizontal distances, hd-1, and hd+1 respectively. If you look clearly, this is exacly like Breadth First Search (BFS) where each node is visited and then all its adjacent nodes will be added to the queue.

We need to maintain a TreeMap to contain the key-value pairs in the sorted order of keys.

Now, whether we just need to put the horizontal distance as the key and node’s value(or data) as the value here. If it’s a new horizontal distance, it will be simply inserted. Or if this horizontal distance already exists as a key, it will be updated with its value. This would go on until each of the keys has got the values of the bottom-most nodes.

Algorithm

  • Create a class for the node of the Tree and a class Pair that comprises a node and its level.
  • We’ll begin with the parent node with a horizontal distance which will be inserted into a queue.
  • We need to perform the level order traversal (also known as Breadth First Search).
  • A TreeMap will be used to sort the entries according to their keys. The horizontal distance will be the key whereas the node’s value will be the value of the keys in the map.
  • Once we get a new horizontal distance, we’ll insert it into the queue. Each of these nodes will have a node’s value and horizontal distance (hd).
  • If the horizontal distance already exists in the map, then it will be updated with the new node’s value whose height is greater than that of its previous one.

Code Implementation

C++

#include<bits/stdc++.h>
using namespace std;

//Create new nodes with the class Node
struct Node{
    /* class Node contains:
         ->node's value
         ->horizontal distance (hd)
         ->left and right child node
    */
	int val; //node's value
	int hd; //horizontal distance
	
	// Left and right child nodes
	Node *left;
	Node *right; 

	// Constructor
	Node(int data){
		val = data;
		hd = INT_MAX;
		left = NULL;
		right = NULL;
	}
};


void bottomView(Node *root){
	if (root == NULL)
		return;
	
	int hd = 0;  //horizontal distance (initialized to 0)
	map<int, int> mp;
	///Creating queue of the type Node
	queue<Node *> q;

	root->hd = hd;
	q.push(root); //adding root to the queue
	while (!q.empty()){
		Node *temp = q.front();
		q.pop(); //remove element from the queue

		hd = temp->hd;
		//Inserts a new key or updates the existing key with the new value
		mp[hd] = temp->val;

		if (temp->left != NULL){
			temp->left->hd = hd-1;
			q.push(temp->left);
		}

		if (temp->right != NULL){
			temp->right->hd = hd+1;
			q.push(temp->right);
		}
	}

	//Using iterator to iterate through the values of the treemap
	for (auto i = mp.begin(); i != mp.end(); ++i)
		cout << i->second << " ";
}


int main(){
	//Create a binary tree
	Node * root = new Node(3);
	
	root -> left = new Node(6);
	root -> right = new Node(12);
	
	root -> left -> left = new Node(5);
	root -> left -> right = new Node(11);
	root -> right -> left = new Node(7);
	root -> right -> right = new Node(8);
	
	root -> left -> right -> left = new Node(20);
	root -> left -> right -> right = new Node(17);
	cout << "The bottom view of the binary tree is: ";
	bottomView(root);
	return 0;
}

Java

import java.util.*;

//Create new nodes with the class Node
class Node{
	/* class Node contains:
         ->node's value
         ->left and right child node
    */
    int val; //node's value

	// Left and right child nodes
    Node left; 
    Node right;
    
	//Constructor
    public Node(int data){
        val = data;
        left = null;
        right = null;
    }
}

public class Main{
	//Creating a pair class for node, and
	//horizontal distance (initialized to 0)
    static class Pair{
        Node node;
        int hd = 0;
    }
    
    public static void bottomView(Node root){

	    //Creating queue of the type Pair
        Queue<Pair> queue = new LinkedList<>();

        Pair p =  new Pair();
        p.node = root;
        p.hd = 0;
        queue.add(p);
        Map<Integer, Integer> map = new TreeMap<>();
        
        while(queue.size() > 0){
            
            Pair temp = queue.remove();
            //Inserts a new key or updates the existing key with the new value
            map.put(temp.hd, temp.node.val);
            
            if(temp.node.left != null){
                Pair lp = new Pair(); //lp-> left pair
                lp.node = temp.node.left;
                lp.hd = temp.hd - 1;
                queue.add(lp);
            }
            
            if(temp.node.right != null){
                Pair rp = new Pair(); //rp -> right pair
                rp.node = temp.node.right;
                rp.hd = temp.hd + 1;
                queue.add(rp);
            }
        }

		//Using iterator to iterate the values of the treemap
		Iterator<Integer> itr = map.values().iterator();
		while(itr.hasNext())
        	 System.out.print(itr.next() + " ");
    }
    
    public static void main(String[] args){
        //Create a binary tree
		Node root = new Node(3);
		
		root.left = new Node(6);
		root.right = new Node(12);
		
		root.left.left = new Node(5);
		root.left.right = new Node(11);
		root.right.left = new Node(7);
		root.right.right = new Node(8);
		
		root.left.right.left = new Node(20);
		root.left.right.right = new Node(17);

		System.out.print("The bottom view of the binary tree is: ");

 		bottomView(root);
    }
}

Python

#Using deque to push and pop the elements
from collections import deque

#Create new nodes with the class Node
class Node:
	def __init__(self, data):
		# class Node contains:
        # ->node's value
        # ->horizontal distance (hd)
        # ->left and right child node
        
		self.val = data # node's value
		self.hd = float('inf') # horizontal distance 
		# Left and right child nodes
		self.left = None
		self.right = None

# Bottom View method
def bottomView(root):
    
    # A base case that returns when there's no node
	if root is None:
		return

	hd = 0  # horizontal distance (initialized to 0)
	
	# Store start and end for the range of all horizontal distances
	# This doesn't require sorting
	start, end = 0, 0
	
	hd_dict = dict()

	# Creating queue to store nodes
	q = deque()

	# Add root node with the horizontal distance (hd=0) to the queue
	root.hd = hd
	q.append(root)

	while q:
		temp = q.popleft()
		
		# Extract the horizontal distance value
		# from the dequeued tree node.
		hd = temp.hd
		
		# Updating the start and end for the range horizontal distance
		start = min(start, hd)
		end = max(end, hd)

		# Inserts a new node or updates the existing node in the dictionary 
		hd_dict[hd] = temp.val

		if temp.left:
			temp.left.hd = hd - 1
			q.append(temp.left)

		if temp.right:
			temp.right.hd = hd + 1
			q.append(temp.right)

	# Traverse the map from the start to the end
	for i in range(start, end+1):
		print(hd_dict[i], end = " ")


if __name__=='__main__':
    
    # Create a binary tree
	root = Node(3);
			
	root.left = Node(6);
	root.right = Node(12);
			
	root.left.left = Node(5);
	root.left.right = Node(11);
	root.right.left = Node(7);
	root.right.right = Node(8);
			
	root.left.right.left = Node(20);
	root.left.right.right = Node(17);
	
	print("The bottom view of the binary tree is: ", end=" ")
	
	bottomView(root)

Output

The bottom view of the binary tree is: 5 20 7 17 8 

Time Complexity

Any comparison to insert, remove, and get an element in a TreeMap takes O(logN) time. So, for N nodes in a tree, it will be:

O(N * log N), where N is the number of nodes present in the binary tree

Space Complexity

Since we have used a map in our code, the space complexity will be: O(N), where N is the number of nodes

Conclusion

  • The bottom view of a binary tree is the representation of all the bottommost nodes in a binary tree.
  • We can simplify our understanding of bottommost nodes with the help of horizontal distances(hd) and the height of the successor in a binary tree.
  • The bottom view of a binary tree can be found in two ways:
    • HashMap and Recursion
    • Queue

FAQs

Q: What is the Bottom View of a Binary Tree?

A: The bottom view of a binary tree refers to all the bottommost nodes of the binary tree. Similarly, there is a top, left, and right view of the binary tree.

Q: How can we Find the Bottom of a Binary Tree?

A: We’ve discussed two ways to find the bottom view of a binary tree:

  • HashMap + Recursion, in which the preorder traversal takes place
  • Queue, with the level order traversal (preorder traversal)

Author