Home CS61A: Trees
Post
Cancel

CS61A: Trees

Trees

  • A data abstraction used for representing hierarchical relationships.
    • Two different metaphors of a tree:
      • Recursive description (wooden trees):
        • A tree possesses a root label and a list of branches.
        • Each branch is a a tree, and also has a root label.
        • A tree with zero branches is a leaf
          • A leaf is also a tree
      • Relative description: (family trees):
        • Each location in a tree is called a node.
        • Each node has a label that can be any value.
        • One node can be the parent/child of another.
          • Ancestors, descendants, siblings, etc.
  • People often refer to labels by their locations: “each parent is the sum of its children”
    Tree Abstraction

Implementing the Tree Abstraction

  • A tree has a root label and a list of branches (this is for the constructor)
    • Each branch is a tree
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def tree(label, branches=[]): # The default value of branches has nothing. So by default, we create a leaf of a tree
    for branch in branches:
        assert is_tree(branch) # Make sure that all branches are trees
    return [label] + list(branches) # Ensure that any sequence of trees is converted into a list

def label(tree):
    return tree[0]

def branches(tree):
    return tree[1:]

def is_tree(tree):
    if type(tree) != list or len(tree) < 1:
        return False
    for branch in branches(tree):
        if not is_tree(branch):
            return False
    return True

def is_leaf(tree):
    return not branches(tree)
1
2
from tree import tree
tree(3, [tree(1), tree(2, [tree(1), tree(1)])])
1
[3, [1], [2, [1], [1]]]

Tree Processing

  • Any function that takes in a tree as an input or returns a tree is likely tree recursive as well.
  • Tree are typically generated programmatically, for instance, a fibonacci tree:
1
2
3
4
5
6
7
def fib_tree(n):
    if n <= 1:
        return tree(n)
    left, right = fib_tree(n-2), fib_tree(n-1)
    return tree(label(left) + label(right), [left, right])

fib_tree(4)
1
[3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]

Tree Processing Uses Recursion

  • In most tree processing functions, processing a leaf is the base case.
    • Recursive case makes a recursive call on each branch, then aggregates the results.
1
2
3
4
5
6
7
def count_leaves(t):
    """Count the leaves of a tree."""
    if is_leaf(t):
        return 1
    return sum([count_leaves(branch) for branch in branches(t)])

count_leaves(fib_tree(4))
1
5

Discussion Question 1

  • Implement leaves, which returns a list of the leaf labels of a tree
1
2
3
4
5
6
def leaves(tree):
    if is_leaf(tree):
        return [label(tree)]
    return sum([leaves(branch) for branch in branches(tree)], [])

leaves(fib_tree(5))
1
[1, 0, 1, 0, 1, 1, 0, 1]

Creating Trees

  • A function that creates a tree from another tree is typically also recursive
1
2
3
4
5
6
7
8
9
10
def increment_leaves(t):
    """Return a tree like t but with leaf labels incremented."""
    def helper(t):
        if is_leaf(t):
            return tree(label(t) + 1)
        return tree(label(t), [helper(branch) for branch in branches(t)])
    return helper(t)

print(fib_tree(4))
print(increment_leaves(fib_tree(4)))
1
2
[3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]
[3, [1, [1], [2]], [2, [2], [1, [1], [2]]]]
1
2
3
4
5
6
def increment(t):
    """Return a tree like t but with leaf labels incremented."""
    return tree(label(t) + 1, [increment(branch) for branch in branches(t)])

print(fib_tree(4))
print(increment(fib_tree(4)))
1
2
[3, [1, [0], [1]], [2, [1], [1, [0], [1]]]]
[4, [2, [1], [2]], [3, [2], [2, [1], [2]]]]

Example: Printing Trees

1
2
3
4
5
6
def print_tree(t, level=0):
    print(level * "   " + "|--" + str(label(t)))
    for b in branches(t):
        print_tree(b, level+1)

print_tree(fib_tree(6))
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
|--8
   |--3
      |--1
         |--0
         |--1
      |--2
         |--1
         |--1
            |--0
            |--1
   |--5
      |--2
         |--1
         |--1
            |--0
            |--1
      |--3
         |--1
            |--0
            |--1
         |--2
            |--1
            |--1
               |--0
               |--1

Example: Summing Paths

  • Some recursive functions build up their result by manipulating the return values of a recursive call.
  • Other recursive functions build up their results by passing information into the recursive call as an argument.
  • Ex: Factorial
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def fact(n):
    if n == 0:
        return 1
    else:
        return n * fact(n-1) # manipulating the return value of a recursive call.
    
def fact_times(n, k):
    """Returns k * n * (n-1) * ... * 1"""
    if n == 0:
        return k
    else:
        return fact_times(n-1, k * n) # Result of the recursive call is the result of the current call.
    

  • In fact_times, the result is being built up with each recursive call, until we hit the base case, which just returns the solution.
    • In fact all the multiplying occurs after we’ve hit the base case.
  • We can apply similar recursive logic to trees
1
2
3
4
5
6
7
8
9
10
11
12
from tree import *

numbers = tree(3, [tree(4), tree(5, [tree(6)])])
haste = tree("h", [tree("a", [tree("s"), tree("t")]), tree("e")])

def print_sums(t, sum_so_far):
    if is_leaf(t):
        print(sum_so_far + label(t))
    for branch in branches(t):
        print_sums(branch, sum_so_far+label(t))

print_sums(haste, "")
1
2
3
has
hat
he

Example: Counting Paths

  • Create a function that count the number of paths within a tree that have a total label sum.
1
2
3
4
5
6
7
8
9
10
def count_paths(t, total):
    """Return the number of paths from the root to any node in tree t for which the labels along the path sum to total."""
    if total-label(t) == 0:
        found = 1
    else:
        found = 0
    return found + sum([count_paths(branch, total-label(t)) for branch in branches(t)])

t = tree(3, [tree(-1), tree(1, [tree(2, [tree(1)]), tree(3)]), tree(1, [tree(-1)])])
count_paths(t, 3)
1
2
This post is licensed under CC BY 4.0 by the author.

CS61A: Data Abstraction

CS61A: Lecture 14