Tree Algorithm Slides
Tree Algorithm Slides
Video Series
Storage and
representation
of trees
Definitions and storage representation
William Fiset
Trees!
What is a tree?
Trees!
What is a tree?
directory
directory directory
directory directory
Trees out in the wild
Social hierarchies
Trees out in the wild
Abstract syntax trees to decompose source
code and mathematical expressions for easy
evaluation.
((x + 6) * -3) > (2 - y)
>
* -
+ -3 2 y
x 6
Trees out in the wild
Every webpage is a tree as an HTML
DOM structure
<html>
<head> <body>
item 1 item 2
Trees out in the wild
The decision outcomes in game theory are often
modeled as trees for ease of representation.
P1
De
es
fe
nf
ct
Co
P2 P2
s
s
De
De
es
es
fe
fe
nf
nf
ct
ct
Co
Co
• Family trees
• File parsing/HTML/JSON/Syntax trees
• Many data structures use/are trees:
• AVL trees, B-tree, red-black trees,
segment trees, fenwick trees, treaps,
suffix trees, tree maps/sets, etc…
• Game theory decision trees
• Organizational structures
• Probabilty trees
• Taxonomies
• etc…
Storing undirected trees
Storing undirected trees
Start by labelling the tree
nodes from [0, n)
2 3 4 5
6 7 8
9
Storing undirected trees
edge list storage
representation:
0 [(0, 1),
(1, 4),
1 (4, 5),
(4, 8),
2 3 4 5 (1, 3),
(3, 7),
6 7 8 (3, 6),
(2, 3),
9 (6, 9)]
Not a
Binary tree Binary tree binary tree
Binary Search Trees (BST)
Related to binary trees are binary search trees
which are trees which satisfy the BST invariant
which states that for every node x:
x.left.value ≤ x.value ≤ x.right.value
6
6
3
2 7
3 8
2
1 4 9
1 1 7 9
3 5 8
3 5 8
root node
Storing rooted trees
Each node also has access to a list
of all its children.
child nodes
Storing rooted trees
Sometimes it’s also useful to maintain a
pointer to a node’s parent node effectively
making edges bidirectional.
parent
current
node
Storing rooted trees
However, this isn’t usually necessary
because you can access a node’s parent on
a recursive function's callback.
Storing rooted trees
If your tree is a binary tree, you can store
it in a flattened array.
i
8 7 1 2
6 5 1 2 3 4 5 6
2 3 4 2 1 7 8 9 10 11 12 13 14
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
9 8 7 6 5 1 2 ∅ 2 3 4 ∅ ∅ 2 1
6 5 1 2 3 4 5 6
2 3 4 2 1 7 8 9 10 11 12 13 14
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
9 8 7 6 5 1 2 ∅ 2 3 4 ∅ ∅ 2 1
8 7 1 2
6 5 1 2 3 4 5 i 6
2 3 4 2 1 7 8 9 10 11 12 13 14
index tree
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
9 8 7 6 5 1 2 ∅ 2 3 4 ∅ ∅ 2 1
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
9 8 7 6 5 1 2 ∅ 2 3 4 ∅ ∅ 2 1
i
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
9 8 7 6 5 1 2 ∅ 2 3 4 ∅ ∅ 2 1
4 3
1 -6 0 7 -4
2 9 8
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0 + 8 - 4 = 9
5
4 3
1 -6 0 7 -4
2 9 8
5
4 3
1 -6 0 7 -4
2 9 8
4 3
1 -6 0 7 -4
2 9 8
5
4 3
1 -6 0 7 -4
2 9 8
5
4 3
1 -6 0 7 -4
2 9 8
5
4 3
1 -6 0 7 -4
2 9 8
2
5
4 3
1 -6 0 7 -4
2 9 8
2
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0 + 8
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0 + 8
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0 + 8
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0 + 8 - 4
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0 + 8 - 4
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0 + 8 - 4
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0 + 8 - 4 = 9
# Sums up leaf node values in a tree.
# Call function like: leafSum(root)
function leafSum(node):
# Handle empty tree case
if node == null:
return 0
if isLeaf(node):
return node.getValue()
total = 0
for child in node.getChildNodes():
total += leafSum(child)
return total
function isLeaf(node):
return node.getChildNodes().size() == 0
# Sums up leaf node values in a tree.
# Call function like: leafSum(root)
function leafSum(node):
# Handle empty tree case
if node == null:
return 0
if isLeaf(node):
return node.getValue()
total = 0
for child in node.getChildNodes():
total += leafSum(child)
return total
function isLeaf(node):
return node.getChildNodes().size() == 0
# Sums up leaf node values in a tree.
# Call function like: leafSum(root)
function leafSum(node):
# Handle empty tree case
if node == null:
return 0
if isLeaf(node):
return node.getValue()
total = 0
for child in node.getChildNodes():
total += leafSum(child)
return total
function isLeaf(node):
return node.getChildNodes().size() == 0
# Sums up leaf node values in a tree.
# Call function like: leafSum(root)
function leafSum(node):
# Handle empty tree case
if node == null:
return 0
if isLeaf(node):
return node.getValue()
total = 0
for child in node.getChildNodes():
total += leafSum(child)
return total
function isLeaf(node):
return node.getChildNodes().size() == 0
# Sums up leaf node values in a tree.
# Call function like: leafSum(root)
function leafSum(node):
# Handle empty tree case
if node == null:
return 0
if isLeaf(node):
return node.getValue()
total = 0
for child in node.getChildNodes():
total += leafSum(child)
return total
function isLeaf(node):
return node.getChildNodes().size() == 0
# Sums up leaf node values in a tree.
# Call function like: leafSum(root)
function leafSum(node):
# Handle empty tree case
if node == null:
return 0
if isLeaf(node):
return node.getValue()
total = 0
for child in node.getChildNodes():
total += leafSum(child)
return total
function isLeaf(node):
return node.getChildNodes().size() == 0
# Sums up leaf node values in a tree.
# Call function like: leafSum(root)
function leafSum(node):
# Handle empty tree case
if node == null:
return 0
if isLeaf(node):
return node.getValue()
total = 0
for child in node.getChildNodes():
total += leafSum(child)
return total
function isLeaf(node):
return node.getChildNodes().size() == 0
# Sums up leaf node values in a tree.
# Call function like: leafSum(root)
function leafSum(node):
# Handle empty tree case
if node == null:
return 0
if isLeaf(node):
return node.getValue()
total = 0
for child in node.getChildNodes():
total += leafSum(child)
return total
function isLeaf(node):
return node.getChildNodes().size() == 0
Problem 2: Tree Height
Find the height of a binary tree. The height
of a tree is the number of edges from the root
to the lowest leaf.
height 0
height 1
height 3
Let h(x) be the height of the subtree
rooted at node x.
b c
e
Let h(x) be the height of the subtree
rooted at node x.
b c
b c
b c
e
?
? ?
? ? ? ?
? ?
?
? ?
? ? ? ?
? ?
?
? ?
? ? ? ?
? ?
?
? ?
? ? 0 ?
? ?
? ?
? ? 0 ?
? ?
?
? ?
? ? 0 0
? ?
? 1
? ? 0 0
? ?
height = max(0, 0) + 1 = 1
?
? 1
? ? 0 0
? ?
?
? 1
? ? 0 0
? ?
?
? 1
? 0 0 0
? ?
? 1
? 0 0 0
? ?
?
? 1
? 0 0 0
? ?
?
? 1
? 0 0 0
0 ?
? 1
? 0 0 0
0 ?
?
? 1
? 0 0 0
0 0
? 1
1 0 0 0
0 0
height = max(0, 0) + 1 = 1
?
2 1
1 0 0 0
0 0
height = max(1, 0) + 1 = 2
3
2 1
1 0 0 0
0 0
height = max(2, 1) + 1 = 3
3
2 1
1 0 0 0
0 0
# The height of a tree is the number of
# edges from the root to the lowest leaf.
function treeHeight(node):
# Handle empty tree case
if node == null:
return -1
return max(treeHeight(node.left),
treeHeight(node.right)) + 1
# The height of a tree is the number of
# edges from the root to the lowest leaf.
function treeHeight(node):
# Handle empty tree case
if node == null:
return -1
return max(treeHeight(node.left),
treeHeight(node.right)) + 1
# The height of a tree is the number of
# edges from the root to the lowest leaf.
function treeHeight(node):
# Handle empty tree case
if node == null:
return -1
return max(treeHeight(node.left),
treeHeight(node.right)) + 1
# The height of a tree is the number of
# edges from the root to the lowest leaf.
function treeHeight(node):
# Handle empty tree case
if node == null:
return -1
return max(treeHeight(node.left),
treeHeight(node.right)) + 1
# The height of a tree is the number of
# edges from the root to the lowest leaf.
function treeHeight(node):
# Handle empty tree case
if node == null:
return -1
return max(treeHeight(node.left),
treeHeight(node.right)) + 1
# The height of a tree is the number of
# edges from the root to the lowest leaf.
function treeHeight(node):
# Handle empty tree case
if node == null:
return -1
return max(treeHeight(node.left),
treeHeight(node.right)) + 1
# The height of a tree is the number of
# edges from the root to the lowest leaf.
function treeHeight(node):
# Return -1 when we hit a null node
# to correct for the right height.
if node == null:
return -1
return max(treeHeight(node.left),
treeHeight(node.right)) + 1
Notice that if we visit the null nodes
our tree is one unit taller.
When we go down the tree we need to correct
for the height added by the null nodes.
-1
+1
-1
+1
+1
-1
+1
+1
+1
-1
+1
+1
+1
+1
-1
+1
+1
+1
+1
-1
1 + 1 + 1 + 1 - 1 = 3
# The height of a tree is the number of
# edges from the root to the lowest leaf.
function treeHeight(node):
# Return -1 when we hit a null node
# to correct for the right height.
if node == null:
return -1
return max(treeHeight(node.left),
treeHeight(node.right)) + 1
Next Video: rooting a tree
Beginner Tree Algorithms
5
4 3
1 -6 0 7 -4
2 9 8
2 + 9 - 6 + 0 + 8 - 4 = 9
Graph Theory
Video Series
Rooting a
tree
William Fiset
Rooting a tree
Sometimes it’s useful to root an undirected
tree to add structure to the problem you’re
trying to solve.
2 Undirected graph
adjacency list:
3
0 1 0 -> [2, 1, 5]
1 -> [0]
2 -> [3, 0]
4 5 3 -> [2]
4 -> [5]
5 -> [4, 6, 0]
6
6 -> [5]
Rooting a tree
Sometimes it’s useful to root an undirected
tree to add structure to the problem you’re
trying to solve.
3 0
0 1
2 1 5
4 5
3 4 6
6
Rooting a tree
Conceptually this is like "picking up" the
tree by a specific node and having all the
edges point downwards.
3 0
0 1
2 1 5
4 5
3 4 6
6
Rooting a tree
3 5
0 1
0 4 6
4 5
1 2
6
3
In some situations it’s also useful to
keep have a reference to the parent
node in order to walk up the tree.
3 5
0 1
0 4 6
4 5
1 2
6
3
Rooting a tree is easily done depth first.
3
0 1
4 5
6
Rooting a tree is easily done depth first.
3 0
0 1
4 5
6
Rooting a tree is easily done depth first.
3 0
0 1
4 5
6
Rooting a tree is easily done depth first.
3 0
0 1
5
4 5
6
Rooting a tree is easily done depth first.
3 0
0 1
5
4 5
6
Rooting a tree is easily done depth first.
3 0
0 1
5
4 5
6
6
Rooting a tree is easily done depth first.
3 0
0 1
5
4 5
6
6
Rooting a tree is easily done depth first.
3 0
0 1
5
4 5
6
6
Rooting a tree is easily done depth first.
3 0
0 1
5
4 5
6 4
6
Rooting a tree is easily done depth first.
3 0
0 1
5
4 5
6 4
6
Rooting a tree is easily done depth first.
3 0
0 1
5
4 5
6 4
6
Rooting a tree is easily done depth first.
3 0
0 1
5
4 5
6 4
6
Rooting a tree is easily done depth first.
3 0
0 1
5 1
4 5
6 4
6
Rooting a tree is easily done depth first.
3 0
0 1
5 1
4 5
6 4
6
Rooting a tree is easily done depth first.
3 0
0 1
5 1
4 5
6 4
6
Rooting a tree is easily done depth first.
3 0
0 1
5 1 2
4 5
6 4
6
Rooting a tree is easily done depth first.
3 0
0 1
5 1 2
4 5
6 4
6
Rooting a tree is easily done depth first.
3 0
0 1
5 1 2
4 5
6 4 3
6
Rooting a tree is easily done depth first.
3 0
0 1
5 1 2
4 5
6 4 3
6
Rooting a tree is easily done depth first.
3 0
0 1
5 1 2
4 5
6 4 3
6
Rooting a tree is easily done depth first.
3 0
0 1
5 1 2
4 5
6 4 3
6
Rooting tree pseudocode
William Fiset
Center(s) of undirected tree
An interesting problem when you have an
undirected tree is finding the tree’s center
node(s). This could come in handy if we wanted
to select a good node to root our tree 😉
Center(s) of undirected tree
An interesting problem when you have an
undirected tree is finding the tree’s center
node(s). This could come in handy if we wanted
to select a good node to root our tree 😉
center(s)
Center(s) of undirected tree
0 9
4
1 2 3
6 5
7 8
Notice that the center is always the middle
vertex or middle two vertices in every
longest path along the tree.
Center(s) of undirected tree
0 9
4
1 2 3
6 5
7 8
Notice that the center is always the middle
vertex or middle two vertices in every
longest path along the tree.
Center(s) of undirected tree
0 9
4
1 2 3
6 5
7 8
Notice that the center is always the middle
vertex or middle two vertices in every
longest path along the tree.
Center(s) of undirected tree
0 9
4
1 2 3
6 5
7 8
Notice that the center is always the middle
vertex or middle two vertices in every
longest path along the tree.
Center(s) of undirected tree
0 9
4
1 2 3
6 5
7 8
Notice that the center is always the middle
vertex or middle two vertices in every
longest path along the tree.
Center(s) of undirected tree
0 9
4
1 2 3
6 5
7 8
Notice that the center is always the middle
vertex or middle two vertices in every
longest path along the tree.
Center(s) of undirected tree
0 9
4
1 2 3
6 5
7 8
Another approach to find the center is to
iteratively pick off each leaf node layer
like we were peeling an onion.
Center(s) of undirected tree
1
1 0 9 1
4
3
4
2 1 2 3
3 6 5
1
7 8 1
1
The orange circles represent the degree of
each node. Observe that each leaf node will
have a degree of 1.
Center(s) of undirected tree
1
1 0 9 1
4
3
4
2 1 2 3
3 6 5
1
7 8 1
1
Center(s) of undirected tree
0
0 0 9 0
4
1
3
1 1 2 3
1 6 5
0
7 8 0
0
1 6 5
0
7 8 0
0
Center(s) of undirected tree
0
0 0 9 0
4
0
0
0 1 2 3
0 6 5
0
7 8 0
0
Center(s) of undirected tree
0 9
4
1 2 3
6 5
7 8
Center(s) of undirected tree
0
2 3 4 5
6 7 8
9
Center(s) of undirected tree
0 1
3
1
4 3
1 2 3 4 5 1
6 7 8
2
1 1
9
1
Center(s) of undirected tree
0 1
3
1
4 3
1 2 3 4 5 1
6 7 8
2
1 1
9
1
Center(s) of undirected tree
0 0
2
1
2 1
0 2 3 4 5 0
6 7 8
1
0 0
9
0
Center(s) of undirected tree
0 0
2
1
2 1
0 2 3 4 5 0
6 7 8
1
0 0
9
0
Center(s) of undirected tree
0 0
1
1
1 0
0 2 3 4 5 0
6 7 8
0
0 0
9
0
Center(s) of undirected tree
0
2 3 4 5
6 7 8
9
Some trees have two centers
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
# g = tree represented as an undirected graph
function treeCenters(g):
n = g.numberOfNodes()
degree = [0, 0, …, 0] # size n
leaves = []
for (i = 0; i < n; i++):
degree[i] = g[i].size()
if degree[i] == 0 or degree[i] == 1:
leaves.add(i)
degree[i] = 0
count = leaves.size()
while count < n:
new_leaves = []
for (node : leaves):
for (neighbor : g[node]):
degree[neighbor] = degree[neighbor] - 1
if degree[neighbor] == 1:
new_leaves.add(neighbor)
degree[node] = 0
count += new_leaves.size()
leaves = new_leaves
return leaves # center(s)
Center(s) of a Tree
0 9
4
1 2 3
5
6
7 8
Graph Theory
Video Series
Isomorphisms
in trees
A question of equality
William Fiset
Graph Isomorphism
The question of asking whether two graphs
G1 and G2 are isomorphic is asking whether
they are structurally the same.
0 a
e b
4 1
3 2 d c
G1 G2
0
1 4
1
2 3
0 2 5 7
4 5
6 7
3 6
0
1 4
1
2 5
3 4 5
0
1 4
1
2 5
3 4 5
2 0 2
3 5 3 4
4 6 5
2 0 2
3 5 3 4
4 6 5
2 0 1 2
Identifying Isomorphic Trees
To select a common node between both trees
we can use what we learned from finding the
center(s) of a tree to help ourselves.
1 4 3 4
2
0 1 2
5
0
T1 3 5
T2
1 4 3 4
2
0 1 2
1 4 3 4
2
0 1 2
4 2
1 3 1 4
0 2 5 0 3 5
1 4 3 4
2
0 1 2
4 2
1 3 1 4
0 2 5 0 3 5
((()())(())) ((()())(()))
1 4 3 4
2
0 1 2
4 2
1 3 1 4
0 2 5 0 3 5
000101100111 000101100111
The tree encoding is simply a sequence of left '(' and
right ')' brackets. However, you can also think of them
as 1’s and 0’s (i.e a large number) if you prefer.
5
0
T1 3 5
T2
1 4 3 4
2
0 1 2
4 2
1 3 1 4
0 2 5 0 3 5
((()())(())) ((()())(()))
It should also be possible to reconstruct
the tree solely from the encoding. This is
left as an exercise to the reader… 😛
Generating the tree encoding
2 1 3
6 7 4 5 8
9
Tree Encoding
Start by assigning all leaf nodes
Knuth tuples: '()'
0
2 1 3
6 7 4 5 8
() () () ()
9
()
Tree Encoding
Start by assigning all leaf nodes
Knuth tuples: '()'
0
2 1 3
6 7 4 5 8
() () () ()
9
()
Tree Encoding
Process all nodes with grayed out children and
combine the labels of their child nodes and
wrap them in brackets.
0
(()()) 2 1 (()) 3
6 7 4 (()) 5 8
() () () ()
9
()
Tree Encoding
Process all nodes with grayed out children and
combine the labels of their child nodes and
wrap them in brackets.
0
(()()) 2 1 (()) 3
6 7 4 (()) 5 8
() () () ()
9
()
Tree Encoding
(()()) 2 1 (()) 3
6 7 4 (()) 5 8
() () () ()
9
()
Tree Encoding
6 7 4 (()) 5 8
() () () ()
9
()
Tree Encoding
Notice that the labels get sorted when
combined, this is important.
0
6 7 4 (()) 5 8
() () () ()
9
()
Tree Encoding
Notice that the labels get sorted when
combined, this is important.
0
6 7 4 (()) 5 8
() () () ()
9
()
Tree Encoding
(((())())(()())(()))
0
6 7 4 (()) 5 8
() () () ()
9
()
Tree Encoding
(((())())(()())(()))
0
6 7 4 (()) 5 8
() () () ()
9
()
Tree Encoding
(((())())(()())(()))
0
6 7 4 (()) 5 8
() () () ()
9
()
Tree Encoding Summary
labels = []
for child in node.children():
labels.add(encode(child))
result = ""
for label in labels:
result += label
labels = []
for child in node.children():
labels.add(encode(child))
result = ""
for label in labels:
result += label
labels = []
for child in node.children():
labels.add(encode(child))
result = ""
for label in labels:
result += label
labels = []
for child in node.children():
labels.add(encode(child))
result = ""
for label in labels:
result += label
labels = []
for child in node.children():
labels.add(encode(child))
result = ""
for label in labels:
result += label
2 3
0 2 5 7
4 5
6 7 3 6
William Fiset
Previous video explaining
identifying isomorphic trees:
Source Code Link
Implementation source code can
be found at the following link:
github.com/williamfiset/algorithms
William Fiset
Graph Theory
Video Series
Lowest Common
Ancestor
Eulerian tour + range minimum query method
William Fiset
Definition
The Lowest Common Ancestor (LCA) of two nodes `a` and
`b` in a rooted tree is the deepest node `c` that has
both `a` and `b` as descendants (where a node can be a
descendant of itself)
0
1 2
3 4
LCA(5, 4) = 2
NOTE: The notion of a LCA also exists for Directed Acyclic Graphs (DAGs),
but today we’re only looking at the LCA in the context of trees.
Definition
The Lowest Common Ancestor (LCA) of two nodes `a` and
`b` in a rooted tree is the deepest node `c` that has
both `a` and `b` as descendants (where a node can be a
descendant of itself)
0
The LCA problem has several applications
in Computer Science, notably:
1 2
NOTE: The notion of a LCA also exists for Directed Acyclic Graphs (DAGs),
but today we’re only looking at the LCA in the context of trees.
Understanding LCA
1 2
3 4 5 6 7
8 9 10 11 12 13
14 15 16
Understanding LCA
LCA(13, 14)
1 2
3 4 5 6 7
8 9 10 11 12 13
14 15 16
Understanding LCA
LCA(13, 14) = 2
1 2
3 4 5 6 7
8 9 10 11 12 13
14 15 16
Understanding LCA
LCA(9, 11)
1 2
3 4 5 6 7
8 9 10 11 12 13
14 15 16
Understanding LCA
LCA(9, 11) = 0
1 2
3 4 5 6 7
8 9 10 11 12 13
14 15 16
Understanding LCA
LCA(12, 12)
1 2
3 4 5 6 7
8 9 10 11 12 13
14 15 16
Understanding LCA
LCA(12, 12) = 12
1 2
3 4 5 6 7
8 9 10 11 12 13
14 15 16
Understanding LCA
You can also find the LCA of more than 2 nodes
1 2
3 4 5 6 7
8 9 10 11 12 13
14 15 16
Understanding LCA
LCA(10, LCA(12, 16)) = 2
1 2
3 4 5 6 7
8 9 10 11 12 13
14 15 16
LCA Algorithms
There are a diverse number of popular algorithms for
finding the LCA of two nodes in a tree including:
1 2
3 4 5
1 2 1
3 2 3
1 2 1
3 2 3
1 2 1
3 2 3
1 2
3 4 5
0 1 2 3 4 5 6 7 8 9 10 11 12
depth
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes
0 Depth 0
1 2 Depth 1
3 4 5 Depth 2
6 Depth 3
0 1 2 3 4 5 6 7 8 9 10 11 12
depth
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes
0 Depth 0
1 2 Depth 1
3 4 5 Depth 2
6 Depth 3
0 1 2 3 4 5 6 7 8 9 10 11 12
depth
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes
0
0 Depth 0
1 2 Depth 1
3 4 5 Depth 2
6 Depth 3
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0
0
0 Depth 0
1 1 2 Depth 1
3 4 5 Depth 2
6 Depth 3
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1
0
0 Depth 0
1 1 2 Depth 1
3 4 5 Depth 2
2
6 Depth 3
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3
0
0 Depth 0
1 1 2 Depth 1
3
3 4 5 Depth 2
2
6 Depth 3
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1
0
0 Depth 0
4
1 1 2 Depth 1
3
3 4 5 Depth 2
2
6 Depth 3
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0
0
0 Depth 0
4
1 1 2 Depth 1
3 5
3 4 5 Depth 2
2
6 Depth 3
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2
0
0 Depth 0
4
1 1 2 Depth 1
3 5
6
3 4 5 Depth 2
2
6 Depth 3
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4
0
0 Depth 0
4
1 1 2 Depth 1
3 5
6
3 4 5 Depth 2
2
6 Depth 3
7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6
0
0 Depth 0
4
1 1 2 Depth 1
3 5
6
3 4 8 5 Depth 2
2
6 Depth 3
7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4
0
0 Depth 0
4
1 1 2 Depth 1
3 5
9
6
3 4 8 5 Depth 2
2
6 Depth 3
7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2
0
0 Depth 0
4
1 1 2 Depth 1
3 5
9
6
3 4 8 5 Depth 2
2 10
6 Depth 3
7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5
0
0 Depth 0
4 11
1 1 2 Depth 1
3 5
9
6
3 4 8 5 Depth 2
2 10
6 Depth 3
7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5 2
0 12
0 Depth 0
4 11
1 1 2 Depth 1
3 5
9
6
3 4 8 5 Depth 2
2 10
6 Depth 3
7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5 2 0
0 12
0 Depth 0
4 11
1 1 2 Depth 1
3 5
9
6
3 4 8 5 Depth 2
2 10
6 Depth 3
7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5 2 0
0 12
0 Q: What is LCA(6, 5)?
4 11
1 1 2
3 5
9
6
3 4 8 5
2 10
6
7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5 2 0
0 12
0 Q: What is LCA(6, 5)?
4 11
1 1 2 1. Find the index position value for the
3 5
nodes `a` and `b` (5 and 6 respectably)
9
6
3 4 8 5
2 10
6
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5 2 0
0 12
0 Q: What is LCA(6, 5)?
4 11
1 1 2 1. Find the index position value for the
3 5
nodes `a` and `b` (5 and 6 respectably)
9
6
3 4 8 5
2. Using the depth array, find the
2 10 index of the minimum value in the range
6 of the indices obtained in step 1
7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5 2 0
0 12
0 Q: What is LCA(6, 5)?
4 11
1 1 2 1. Find the index position value for the
3 5
nodes `a` and `b` (5 and 6 respectably)
9
6
3 4 8 5
2. Using the depth array, find the
2 10 index of the minimum value in the range
6 of the indices obtained in step 1
7
Query the range [7, 10] in the depth array to find the index of the
minimum value. This can be done in O(1) with a Sparse Table. For
this example, the index is `9` with a value of `1`
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5 2 0
0 12
0 Q: What is LCA(6, 5)?
4 11
1 1 2 1. Find the index position value for the
3 5
nodes `a` and `b` (5 and 6 respectably)
9
6
3 4 8 5
2. Using the depth array, find the
2 10 index of the minimum value in the range
6 of the indices obtained in step 1
7
3. Using the index obtained in step 2,
find the LCA of `a` and `b` in the
`nodes` array.
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5 2 0
0 12
0
4 11
1 1 2
3 5
9
6
3 4 8 5
2 10
6
7
If you recall, step 1 required finding the index
position for the two nodes with ids `a` and `b`.
6
7
6
7
6
7
6
7
6
7
The reason the selection of the inverse index mapping
doesn’t matter is that it does not affect the value
obtained from the Range Minimum Query (RMQ) in step 2
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
0 12
0
4 11
1 1 2
3 5
9
6
3 4 8 5
2 10
6
7
Suppose that for the LCA(1, 2) we selected index 1
for node 1 and index 9 for node 2, meaning the range
[1, 9] in the depth array for the RMQ.
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
0 12
0
4 11
1 1 2
3 5
9
6
3 4 8 5
2 10
6
7
Even though the range [1, 9] includes some subtrees of the nodes 1
and 2, the depths of the subtree nodes are always more than the
depths of nodes 1 and 2, so the value of the RMQ remains unchanged.
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
0 12
0
4 11
1 1 2
3 5
9
6
3 4 8 5
2 10
6
7
You may think that choosing the index values 3 and 5 for nodes
1 and 2 would be better choice since the interval [3, 5] is
smaller. However, this doesn’t matter since RMQs take O(1)
when using a sparse table.
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
To maintain an inverse mapping,
0 we’re going to need to keep track of
some additional information, namely
an inverse map I will call `last`.
1 2
3 4 5
6 0 1 2 3 4 5 6
last
0 1 2 3 4 5 6 7 8 9 10 11 12
depth
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes
0
1 2
3 4 5
6 0 1 2 3 4 5 6
last
0 1 2 3 4 5 6 7 8 9 10 11 12
depth
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes
0
0
1 2
3 4 5
6 0 1 2 3 4 5 6
last 0
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0
0
0
1 1 2
3 4 5
6 0 1 2 3 4 5 6
last 0 1
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1
0
0
1 1 2
3 4 5
2
6 0 1 2 3 4 5 6
last 0 1 2
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3
0
0
1 1 2
3
3 4 5
2
6 0 1 2 3 4 5 6
last 0 3 2
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1
0
0
4
1 1 2
3
3 4 5
2
6 0 1 2 3 4 5 6
last 4 3 2
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0
0
0
4
1 1 2
3 5
3 4 5
2
6 0 1 2 3 4 5 6
last 4 3 5 2
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2
0
0
4
1 1 2
3 5
6
3 4 5
2
6 0 1 2 3 4 5 6
last 4 3 5 2 6
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4
0
0
4
1 1 2
3 5
6
3 4 5
2
6 0 1 2 3 4 5 6
7 last 4 3 5 2 6 7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6
0
0
4
1 1 2
3 5
6
3 4 8 5
2
6 0 1 2 3 4 5 6
7 last 4 3 5 2 8 7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4
0
0
4
1 1 2
3 5
9
6
3 4 8 5
2
6 0 1 2 3 4 5 6
7 last 4 3 9 2 8 7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2
0
0
4
1 1 2
3 5
9
6
3 4 8 5
2 10
6 0 1 2 3 4 5 6
7 last 4 3 9 2 8 10 7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5
0
0
4 11
1 1 2
3 5
9
6
3 4 8 5
2 10
6 0 1 2 3 4 5 6
7 last 4 3 11 2 8 10 7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5 2
0 12
0
4 11
1 1 2
3 5
9
6
3 4 8 5
2 10
6 0 1 2 3 4 5 6
7 last 12 3 11 2 8 10 7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5 2 0
0 12
0
4 11
1 1 2
3 5
9
6
3 4 8 5
2 10
6 0 1 2 3 4 5 6
7 last 12 3 11 2 8 10 7
0 1 2 3 4 5 6 7 8 9 10 11 12
depth 0 1 2 1 0 1 2 3 2 1 2 1 0
0 1 2 3 4 5 6 7 8 9 10 11 12
nodes 0 1 3 1 0 2 4 6 4 2 5 2 0
class TreeNode:
visit(node, node_depth)
for (TreeNode child in node.children):
dfs(child, node_depth + 1)
visit(node, node_depth)
visit(node, node_depth)
for (TreeNode child in node.children):
dfs(child, node_depth + 1)
visit(node, node_depth)
visit(node, node_depth)
for (TreeNode child in node.children):
dfs(child, node_depth + 1)
visit(node, node_depth)
visit(node, node_depth)
for (TreeNode child in node.children):
dfs(child, node_depth + 1)
visit(node, node_depth)
visit(node, node_depth)
for (TreeNode child in node.children):
dfs(child, node_depth + 1)
visit(node, node_depth)
visit(node, node_depth)
for (TreeNode child in node.children):
dfs(child, node_depth + 1)
visit(node, node_depth)
l = min(last[index1], last[index2])
r = max(last[index1], last[index2])
l = min(last[index1], last[index2])
r = max(last[index1], last[index2])
l = min(last[index1], last[index2])
r = max(last[index1], last[index2])
l = min(last[index1], last[index2])
r = max(last[index1], last[index2])
l = min(last[index1], last[index2])
r = max(last[index1], last[index2])
4
1
1 2
3 5
6
3 4 5
8
6
7
Graph Theory
Video Series
Lowest Common
Ancestor
source code
Eulerian tour + range minimum query method
William Fiset
Previous video explaining the
LCA problem:
Source Code Link
Implementation source code can
be found at the following link:
github.com/williamfiset/algorithms
William Fiset