Binary Tree
Introduction
A tree
is a frequently used data structure to simulate a hierarchical tree-like structure.
Each node of the tree will a value
and list of references to otehr nodes which are called child nodes
.
From a graph
view, a tree
can also be described as a DAG (directed acyclic graph)
which has N
nodes and (N-1)
edges.
A binary tree
is a tree data structure where each node can have maximum 2 children only.
A binary tree where all internal nodes (i.e. except the leaf nodes) have exactly 2 children are called Complete Binary Tree
.
Traversals
Pre-Order:
- Traverse the root first then traverse the left subtree. Finally traverse the right subtree
In-Order:
- Traverse the left subtree first. Then visit the root node. Finally traverse the right subtree.
- In binary search tree; in-order traversal gives the whole tree nodes in a sorted order.
Post-Order:
- Traverse the left subtree first, then traverse the right subtree*. Finally visit the root node**
Examples
25: Depth First values
Depth First Values
Write a function, depth_first_values
, that takes in the root of a binary tree.
The function should return a list containing all values of the tree in depth-first order.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
e = Node('e')
f = Node('f')
g = Node('g')
a.left = b
a.right = c
b.left = d
b.right = e
c.right = f
e.left = g
# a
# / \
# b c
# / \ \
# d e f
# /
# g
depth_first_values(a)
# -> ['a', 'b', 'd', 'e', 'g', 'c', 'f']
|
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | # class Node:
# def __init__(self, val):
# self.val = val
# self.left = None
# self.right = None
def depth_first_values(root):
"""recursive solution Depth First Traversal"""
## base case
if root is None:
return []
left_values = depth_first_values(root.left)
right_values = depth_first_values(root.right)
## in DFS, the visiting order is
## root, left-child, right-child
return [root.val, *left_values, *right_values]
|
26: Breadth First Values
Breadth First Values
Write a function, breadth_first_values
, that takes in the root of a binary tree.
The function should return a list containing all values of the tree in breadth-first order.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
e = Node('e')
x = Node('x')
a.right = b
b.left = c
c.left = x
c.right = d
d.right = e
# a
# \
# b
# /
# c
# / \
# x d
# \
# e
breadth_first_values(a)
# -> ['a', 'b', 'c', 'x', 'd', 'e']
|
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 | # class Node:
# def __init__(self, val):
# self.val = val
# self.left = None
# self.right = None
def breadth_first_values(root):
## base case for empty tree
if root is None:
return []
## using double-ended queue struct
from collections import deque
## step-1: add the root into the queue
queue = deque([root])
visited = []
while queue:
## grabbing current visited node
current_node = queue.popleft()
## put that node in the visited list
visited.append(current_node.val)
## and add their children (if present) into the queue
if current_node.left:
queue.append(current_node.left)
if current_node.right:
queue.append(current_node.right)
return visited
|
27: Tree Includes
Problem
Write a function, tree_includes, that takes in the root of a binary tree and a target value.
The function should return a boolean indicating whether or not the value is contained in the tree.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | a = Node("a")
b = Node("b")
c = Node("c")
d = Node("d")
e = Node("e")
f = Node("f")
a.left = b
a.right = c
b.left = d
b.right = e
c.right = f
# a
# / \
# b c
# / \ \
# d e f
tree_includes(a, "e") # -> True
|
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | # class Node:
# def __init__(self, val):
# self.val = val
# self.left = None
# self.right = None
def tree_includes(root, target):
## base case of leaf-nodes
if not root: return False
## success
if root.val == target: return True
## else search in the children tree
return tree_includes(root.left, target) or tree_includes(root.right, target)
|
28: Tree Sum
Problem
Write a function, tree_sum
, that takes in the root of a binary tree that contains number values.
The function should return the total sum of all values in the tree.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | a = Node(3)
b = Node(11)
c = Node(4)
d = Node(4)
e = Node(-2)
f = Node(1)
a.left = b
a.right = c
b.left = d
b.right = e
c.right = f
# 3
# / \
# 11 4
# / \ \
# 4 -2 1
tree_sum(a) # -> 21
|
Solution
| # class Node:
# def __init__(self, val):
# self.val = val
# self.left = None
# self.right = None
def tree_sum(root):
if not root: return 0 ## base-case of leaf nodes
left_sum = tree_sum(root.left) ## sum of left subtree
right_sum = tree_sum(root.right) ## sum of right subtree
return root.val + left_sum + right_sum
|
29: Binary Tree Min Value
Minimum value in a binary tree
Write a function, tree_min_value, that takes in the root of a binary tree that contains number values.
The function should return the minimum value within the tree.
You may assume that the input tree is non-empty.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | a = Node(3)
b = Node(11)
c = Node(4)
d = Node(4)
e = Node(-2)
f = Node(1)
a.left = b
a.right = c
b.left = d
b.right = e
c.right = f
# 3
# / \
# 11 4
# / \ \
# 4 -2 1
tree_min_value(a) # -> -2
|
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13 | # class Node:
# def __init__(self, val):
# self.val = val
# self.left = None
# self.right = None
def tree_min_value(root):
import math
if not root: return math.inf ## base case for leaf nodes's children
left_min = tree_min_value(root.left) ## minimum value in left subtree
right_min = tree_min_value(root.right) ## minimum value in right subtree
return min(root.val, left_min, right_min) ## return minimum of root, lef, & right subtrees
|
30: Root-to-Leaf path w/ MAX sum
Problem
Write a function max_path_sum()
that takes in the root of a Binary Tree
that conatins number values. The functions should return the maximum sum of any
root-to-leaf path in the tree.
Assumption: The tree is non-empty
An example of the scenario is shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | a = Node(-1)
b = Node(-6)
c = Node(-5)
d = Node(-3)
e = Node(0)
f = Node(-13)
g = Node(-1)
h = Node(-2)
a.left = b
a.right = c
b.left = d
b.right = e
c.right = f
e.left = g
f.right = h
# -1
# / \
# -6 -5
# / \ \
# -3 0 -13
# / \
# -1 -2
max_path_sum(a) # -> -8
|
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | import math
class Node:
def __init__(self, value):
self.val = value
self.left = None
self.right = None
## RECURSIVE Solution
def max_path_sum(root):
## base case (None node)
if root is None:
return -math.inf
## base case (leaf node)
if root.left == None and root.right == None:
return root.val
left_sum = max_path_sum(root.left)
right_sum = max_path_sum(root.right)
return root.val + max(left_sum, right_sum)
|
31: Tree Path Finder
Problem
Write a function path_finder
that takes in the root of a BT and a target value
The function should return an array representing the path to teh target value.
If the target value is not found, then return None
.
Assumption: Every node in teh tree contains unique value.
Sample example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | a = Node("a")
b = Node("b")
c = Node("c")
d = Node("d")
e = Node("e")
f = Node("f")
g = Node("g")
h = Node("h")
a.left = b
a.right = c
b.left = d
b.right = e
c.right = f
e.left = g
f.right = h
# a
# / \
# b c
# / \ \
# d e f
# / \
# g h
path_finder(a, "h") # -> ['a', 'c', 'f', 'h']
|
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 | class Node:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def _path_finder(root, target):
## base case (None node)
if root is None:
return None
## base case (node found)
if root.val == target:
return [root.val]
## recuse over the left child
left_path = _path_finder(root.left, target)
if left_path is not None:
## target found in the left child subtree
left_path.append(root.val)
return left_path
## recurse over the right child
right_path = _path_finder(root.right, target)
if right_path is not None:
## target found in the left child subtree
right_path.append(root.val)
return right_path
return None ## edge case
def path_finder(root, target):
path = _path_finder(root, target)
if path is None:
return None
return path[::-1] ## return the path in reverse order (from root to target)
|
32: Tree Value Count
Problem
Write a function, tree_value_count, that takes in the root of a binary tree and a target value.
The function should return the number of times that the target occurs in the tree.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | a = Node(12)
b = Node(6)
c = Node(6)
d = Node(4)
e = Node(6)
f = Node(12)
a.left = b
a.right = c
b.left = d
b.right = e
c.right = f
# 12
# / \
# 6 6
# / \ \
# 4 6 12
tree_value_count(a, 6) # -> 3
|
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | class Node:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
## RECURSIVE DFS
## time = O(n)
## space = O(n)
def tree_value_count(root, target):
## base case (None node)
if root is None:
return 0
left_count = tree_value_count(root.left, target)
right_count = tree_value_count(root.right, target)
if root.val == target:
return 1 + left_count + right_count
return left_count + right_count
|
33: Height of a BT
Problem
Write a function, how_high()
, that takes in the root of a binary tree.
The function should return a number representing the height of the tree.
The height of a binary tree is defined as the maximal number of edges from the root node to any leaf node.
If the tree is empty, return -1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
e = Node('e')
f = Node('f')
g = Node('g')
a.left = b
a.right = c
b.left = d
b.right = e
c.right = f
e.left = g
# a
# / \
# b c
# / \ \
# d e f
# /
# g
how_high(a) # -> 3
|
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | class Node:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
## RECURSIVE approach
def how_high(root):
## base case (None node)
if root is None:
return -1 ## see definition of height of a BT above
left_height = how_high(root.left)
right_height = how_high(root.right)
return 1 + max(left_height, right_height)
|
34: Botton Right Value
Problem
Write a function, bottom_right_value()
, that takes in the root of a binary tree.
The function should return the right-most value in the bottom-most level of the tree.
You may assume that the input tree is non-empty.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | a = Node(-1)
b = Node(-6)
c = Node(-5)
d = Node(-3)
e = Node(-4)
f = Node(-13)
g = Node(-2)
h = Node(6)
a.left = b
a.right = c
b.left = d
b.right = e
c.right = f
e.left = g
e.right = h
# -1
# / \
# -6 -5
# / \ \
# -3 -4 -13
# / \
# -2 6
bottom_right_value(a) # -> 6
|
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | from collections import deque
def bottom_right_value(root):
queue = deque([root])
current = None
while queue:
current = queue.popleft()
if current.left is not None:
queue.append(current.left)
if current.right is not None:
queue.append(current.right)
return current.val
|
35: All tree paths
Write a function, all_tree_paths()
, that takes in the root of a binary tree.
The function should return a 2-Dimensional list where each subarray represents
a root-to-leaf path in the tree.
The order within an individual path must start at the root and end at the leaf,
but the relative order among paths in the outer list does not matter.
You may assume that the input tree is non-empty.
An example use case is shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
e = Node('e')
f = Node('f')
g = Node('g')
h = Node('h')
i = Node('i')
a.left = b
a.right = c
b.left = d
b.right = e
c.right = f
e.left = g
e.right = h
f.left = i
# a
# / \
# b c
# / \ \
# d e f
# / \ /
# g h i
all_tree_paths(a) # ->
# [
# [ 'a', 'b', 'd' ],
# [ 'a', 'b', 'e', 'g' ],
# [ 'a', 'b', 'e', 'h' ],
# [ 'a', 'c', 'f', 'i' ]
# ]
|
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 | # class Node:
# def __init__(self, val):
# self.val = val
# self.left = None
# self.right = None
## Recursive DFS
## during DFS let the nodes return two lists which contains
## the two possible paths (left & right)
## branch it according to values it receives from each branch
def _all_tree_paths(root):
## base case (None node)
if root is None:
return [] ## return empty list as there its a null node
left_paths = _all_tree_paths(root.left)
right_paths = _all_tree_paths(root.right)
paths = left_paths + right_paths
## base-case (leaf-node)
if len(paths) == 0:
paths.append([root.val])
return paths
for path in paths:
path.append(root.val)
return paths
def all_tree_paths(root):
all_paths = _all_tree_paths(root)
return [path[::-1] for path in all_paths]
|
36: Tree levels
Write a function, tree_levels()
, that takes in the root of a binary tree.
The function should return a 2D list where each sublist represents a level of the tree.
An example use-case is shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
e = Node('e')
f = Node('f')
g = Node('g')
h = Node('h')
i = Node('i')
a.left = b
a.right = c
b.left = d
b.right = e
c.right = f
e.left = g
e.right = h
f.left = i
# a
# / \
# b c
# / \ \
# d e f
# / \ /
# g h i
tree_levels(a) # ->
# [
# ['a'],
# ['b', 'c'],
# ['d', 'e', 'f'],
# ['g', 'h', 'i']
# ]
|
Solution
...