DSA course menu
Trees and Binary Trees: Terminology and Python Implementation
Learn the tree data structure: root, parent, child, leaf, depth and height, binary trees and their types, building a binary tree in Python, and computing size, height and leaf count recursively.
On this page
A tree stores data in a hierarchy: one item at the top branches into children, which branch into their own children, and so on. File systems, HTML pages, company org charts and the moves in a game are all trees.
CEO ← root
/ \
CTO CFO
/ \ \
Dev1 Dev2 Accountant ← leavesVocabulary#
A tree with n nodes always has exactly n − 1 edges, and there is exactly one path between any two nodes.
Binary trees#
In a binary tree, every node has at most two children, called left and right. It is the most important kind of tree in DSA.
class TreeNode:
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
# 1
# / \
# 2 3
# / \ \
# 4 5 6
root = TreeNode(1,
TreeNode(2, TreeNode(4), TreeNode(5)),
TreeNode(3, None, TreeNode(6)))
print(root.value, root.left.value, root.right.right.value)1 2 6Thinking recursively about trees#
Every subtree is itself a tree, so most tree functions follow one recipe: handle the empty tree, then combine the answers for the left and right subtrees.
class TreeNode:
def __init__(self, value, left=None, right=None):
self.value, self.left, self.right = value, left, right
root = TreeNode(1,
TreeNode(2, TreeNode(4), TreeNode(5)),
TreeNode(3, None, TreeNode(6)))
def size(node):
if node is None:
return 0
return 1 + size(node.left) + size(node.right)
def height(node):
if node is None:
return -1 # so a single node has height 0
return 1 + max(height(node.left), height(node.right))
def count_leaves(node):
if node is None:
return 0
if node.left is None and node.right is None:
return 1
return count_leaves(node.left) + count_leaves(node.right)
def tree_sum(node):
return 0 if node is None else node.value + tree_sum(node.left) + tree_sum(node.right)
print("size", size(root))
print("height", height(root))
print("leaves", count_leaves(root))
print("sum", tree_sum(root))size 6
height 2
leaves 3
sum 21Each function visits every node once: O(n) time, and O(h) stack space for a tree of height h.
Types of binary trees#
- Full: every node has 0 or 2 children.
- Complete: every level is completely filled except possibly the last, which is filled from the left. Heaps are complete trees.
- Perfect: all internal nodes have 2 children and all leaves are at the same depth. A perfect tree of height h has 2^(h+1) − 1 nodes.
- Balanced: the heights of the left and right subtrees of every node differ by at most 1. Balanced trees have height O(log n).
- Degenerate: every node has one child, so the tree is really a linked list with height n − 1.
The height matters because most tree operations cost O(h). With a million nodes, a balanced tree is about 20 levels deep; a degenerate one is a million.
import math
for n in [7, 1_000, 1_000_000]:
print(f"{n:>9} nodes: balanced height ~{math.floor(math.log2(n))}, degenerate height {n - 1}") 7 nodes: balanced height ~2, degenerate height 6
1000 nodes: balanced height ~9, degenerate height 999
1000000 nodes: balanced height ~19, degenerate height 999999Common mistakes#
Practice#
Practice exercise
Write max_value(node) that returns the largest value in a binary tree (or None for an empty tree), and mirror(node) that swaps the left and right children of every node in place.
Show hint
max_value: compare the node's value with the maximum of each non-empty subtree. mirror: swap node.left and node.right, then mirror both subtrees.
Show solution
class TreeNode:
def __init__(self, value, left=None, right=None):
self.value, self.left, self.right = value, left, right
def max_value(node):
if node is None:
return None
best = node.value
for child in (node.left, node.right):
m = max_value(child)
if m is not None and m > best:
best = m
return best
def mirror(node):
if node is not None:
node.left, node.right = node.right, node.left
mirror(node.left)
mirror(node.right)
def show(node):
if node is None:
return "."
return f"({show(node.left)} {node.value} {show(node.right)})"
root = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(9)), TreeNode(3))
print(max_value(root))
print(show(root))
mirror(root)
print(show(root))9
(((. 4 .) 2 (. 9 .)) 1 (. 3 .))
((. 3 .) 1 ((. 9 .) 2 (. 4 .)))Key takeaway#
Next: the different orders for visiting nodes, Tree Traversals.
Finished this lesson?