1

I came across this algorithm to find the height of a binary tree. Is anyone able to explain how it works? Specifically, I'm confused about the recursive calls inside the max function. What is max comparing on each iteration and how is the output able to be added to 1 without causing a TypeError? Thanks!

def get_height(root):
if root is None: 
    return 0
return 1 + max(get_height(root.left), get_height(root.right))
4
  • 1
    This code adds 1 for each level of the tree and returns the longest (max) route to a leaf (a node with no children). Commented Sep 5, 2018 at 23:53
  • 1
    each call of get_height is independent. Thus root is a binding unique for each one. The recursion is treated the same way as any other function and thus you call the get_height on the left and right child nodes (which is what root gets to be for those) and you get the two heights of the subtrees except your node so you add 1 to the highest. I have no idea why you expect a type error. Commented Sep 5, 2018 at 23:56
  • max returns a number and get_height returns a number (0 for the root case, or 1 + max(x, y) otherwise), why would that be a TypeError? Commented Sep 5, 2018 at 23:57
  • 1
    It might help understand this if you run it with a small tree at PythonTutor.com, where you can step through it in an interactive visualizer and see all the calls and how they stack up. Commented Sep 6, 2018 at 0:14

2 Answers 2

3

Adding some line to the code will help to figure out what's going on.
Here a class representing a Tree.

class Tree:
    def __init__(self,id,left=None,right=None):
        self.id = id
        self.left = left
        self.right = right

And a function to find the height.

def get_height(root):
    if root is None: 
        return 0

    my_height = 1 + max(get_height(root.left), get_height(root.right))
    print("id :{} height: {}".format(root.id,my_height))
    return my_height

t = Tree(1,Tree(2,Tree(4),Tree(5)),Tree(3))

t can be seen as follows.

     1
    / \
   2   3
  / \   
 4   5 

When calling get_height(t) this is the output:

id :4 height: 1
id :5 height: 1
id :2 height: 2
id :3 height: 1
id :1 height: 3

The calls start from the root until after having reached a leaf node the function will be called with None.
Suppose to have reached a leaf (e.g., Node 4). The results on get_height() on Node 4 would be 1 + max(get_height(None),get_height(None)) = 1 + max(0,0) =1.
Node 4 thus returns 1. The caller (the parent of Node 4, i.e., Node 2) would get 1 from Node 4 and 1 from Node 5.
Thus it will conclude that its height is 1+max(1,1)=2 and return this value to the parent node.
In the same way, Node 1 (the root) would get 2 from Node 2 and 1 from Node 3 and compute its height as 1+max(1,2)=3.
The height of the Tree is 3, as the root as height 3.

Sign up to request clarification or add additional context in comments.

2 Comments

based on your answer and after doing some research on the "call stack" I think my problem stemmed from not realizing that the last call is the first to be returned. Then your answer makes perfect sense. If the first call were the first to be returned then max would be comparing two nodes not two numbers, which is why I thought there might be a TypeError. Am I getting it?
yes, you continue calling the function get_height in max(...). When you reach a base case you go back to the caller that may be the function itself. You really compute the max when you have all both values, i.e., height for left and for right. This process goes on until you come back to the root of the original tree to compute the final result and return to the caller outside the function.
2

Specifically, I'm confused about the recursive calls inside the max function. What is max comparing on each iteration and how is the output able to be added to 1 without causing a TypeError?

You’re calling a function twice, and passing the return values of those two function calls to max. So, max is comparing whatever this function returns.

If that isn’t clear, let’s break down that one-liner:

left_height = get_height(root.left)
right_height = get_height(root.right)
max_height = max(left_height, right_height)
return 1 + max_height

Since the function we’re calling always returns an integer, max is comparing two integers, which gives you the larger of the two integers. Which is something we can obviously add 1 to.

In other words, the height of the tree is 1 more than the height of whichever subtree is taller.


You may think I cheated there. How can I prove that the function always returns an integer, when I had to assume that to prove it?

The key is that recursion is inductive. If you don’t know much math, that’s a fancy way of saying this:

  • If the base case always returns an integer,
  • and recursion always calls a smaller case
  • and if we always return an integer as long as all smaller cases return an integer
  • then we always return an integer.

The first part is easy: if it’s None, we return 0.

The second part comes from the definition of a tree: the left branch of a tree and the right branch of a tree are always smaller subtrees of the tree. (This wouldn’t be true for a cyclic graph, where root.left could be root or its grandparent.)

The third part is the explanation from the first half of the answer.

And we’re done.

6 Comments

what do you mean by "recursion always calls a smaller case"?
@fen If your recursive calls are always on a "smaller" thing than the caller's input… where "smaller" can mean a lower natural number, a subsequence, a subtree… as long as it makes the base case "smallest" and you use "smaller" consistently in the next condition, the induction works.
@fen It's hard to explain abstractly, but the concrete explanation for your binary tree (the paragraph starting with "The second part") should be a lot easier to understand.
I think that makes sense. Like I said to newbie above, I think my problem stemmed from not realizing that the last call is the first to be returned (i.e. the calls are stacked) and that seems to be what you're describing here too?
@fen Yeah, understanding the call stack is pretty important for understanding recursion. If you haven’t tried PythonTutor.com, you really should—it’s a lot easier for this stuff to click if you can see it visually.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.