# Class 26: Recursion II

## Objectives for today

• Implement recursion with pending operations
• Recursively draw images with Turtle
• Apply techniques for improving efficiency of recursive algorithms

## Review: How to Write a Recursive Function

1. Define the function header, including the parameters
2. Define the recursive case
3. Define the base case
4. Put it all together

## Recursion with pending operations

Consider the following code. What is this code doing? What is the output of the call go_back(3)? (See lecture21.py for the docstring.)

def go_back(n):
if n == 0:
print("Stop")
else:
print("Go", n)
go_back(n-1)
print("Back", n)


The line print("Back", n) is an example of a “pending operation”, that is, an operation that gets performed when control continues after the recursive call. Check this code out in Python Tutor.

## Limits on Recursion

Python has a limit on the height of the call stack (we saw that last time when we attempted “infinite” recursion). Depending on how many recursive calls you make, e.g. factorial(1000), you may hit that limit triggering a RecursionError (even without infinite recursion). You can increase the limit with a function in the sys module, like shown below, thus enabling us to successfully compute factorial(1000).

import sys
sys.setrecursionlimit(10000)


## Recursive Drawing with Turtle

### Spiral

Consider the following code to draw a spiral. In each iteration it draws a segment, turns left, and then recursively draws shorter segments (only 95% of the length).

def spiral1(length, levels):
"""
Draw a spiral with 'levels' segments with initial 'length'
"""
# Implicit base case: do nothing if levels == 0
if levels > 0:
forward(length)
left(30)
spiral1(0.95 * length, levels-1) # Recurse


Try running the code with say, spiral1(80, 45). Notice that the turtle stops and remains where the drawing ends at the inner tip of the spiral.

Let’s revise this code to “undo” the calls to forward and left. After the recursive call to spiral2(0.95 * length, levels-1), undo the left turn by turning right, and undo the forward by moving backward:

def spiral2(length, levels):
"""
Draw a spiral with 'levels' segments with initial 'length'
"""
# Implicit base case: do nothing if levels == 0
if levels > 0:
forward(length)
left(30)
spiral2(0.95 * length, levels-1) # Recurse
right(30)
backward(length)


If I started with the turtle at (0,0), what is the position of the turtle after invoke this function, e.g. spiral2(80, 45)? The turtle ends up back at (0,0) because we are “retracing” the movements after the recursive call. This is a common pattern in recursive functions in which we “reset” the state after a recursive call using state maintained in the call stack.

### Drawing Trees and “Broccoli”

Check out the draw_tree and broccoli functions in lecture21.py:

def draw_tree(length, levels):
"""
Args:
length: length of initial tree trunk
levels: number of tree trunk segments from bottom to top
"""
if levels > 0:
forward(length)                  # draw tree branch
right(45)
draw_tree(length/2, levels-1)    # draw right subtree
left(45*2)                       # undo right turn, then turn left again
draw_tree(length/2, levels-1)    # draw left subtree
right(45)                        # undo left turn
backward(length)                 # trace back down the tree branch


The above function draws nothing if levels is 0. This is another example of an implicit base case that simply returns (terminating execution) without doing anything.

For level == 1, the turtle moves forward, turns right, draws a “level 0” tree (i.e., nothing), turns left, draws another “level 0” tree (i.e., nothing), turns right so it is facing the original direction, then goes backward to undo the original forward.

For level == 2, the turtle moves forward, turns right, draws “level 1” tree (i.e., a line), turns left, draws another “level 1 “tree (i.e., a line), turns right so it is facing the original direction, then goes backward to undo the original forward.

At any level, the turtle moves forward, turns right, draws a level n-1 tree, turns left, draws another level n-1 tree, turns right so it is facing the original direction, then goes backward to undo the original forward.

Here are the trees drawn for levels 1, 2, 3, and 4: Drawing the “broccoli” is very similar:

1. Define the function header, including the parameters

def broccoli(length)

2. Define the recursive case

Draw a line with three smaller broccolis at the end, one on the same heading, the other two offset by 20 degrees (by smaller we mean shorter “stem”).

3. Define the base case

Below a certain length, don’t draw a line (and more broccolis), just draw a dot.

4. Put it all together

Why do we include backward(length) at the end of the recursive function? This ensures that the turtle ends up where it started at the end of each subtree that is drawn. Notice that this backward command matches the forward command a few lines up, and these are the only movements of the turtle!

How many lines does this program draw? Lots! How could we figure out exactly how many? We could return the count of the number of lines drawn.

• How many lines are drawn in the base case? Zero.
• How many lines are drawn in the recursive case? One plus the lines drawn by each of the recursive calls.

## How do I make my problem smaller?

There is no “one” way to make the problem smaller. However we have seen several common patterns that we can use as we implement our recursive functions:

• If we are working with numbers, e.g., “lengths” or “levels”, we can subtract 1, divide by 2 or otherwise make the number smaller
• If working with strings or lists (which are indexable), we can split the input into the first (or last) element and the “rest” of the string or list.

## Understanding and Improving Efficiency

Let’s develop a recursive implementation for power, with parameter base and exp (exponent):

1. Define the function header, including the parameters

def power(base, exp)

2. Define the recursive case

$$base^{exp} = base * base^{(exp-1)}$$

3. Define the base case

$$base^0 = 1$$

4. Put it all together

def power(base, exp):
if exp == 0:
return 1
else:
return base * power(base, exp-1)


What is the time complexity of this implementation, i.e. how many times is power called? exp + 1 times, or linear with the size of exp.

What is the space complexity of this implementation, i.e. how will the memory grow as we increase exp? Also linearly. Why? Every time we invoke a function, Python creates a frame on the call stack. Each frame on the call stack requires some amount of space and the number of frames grows with the size of the input.

Can we do better?

Recall that $$base^{exp}=base^{exp/2}*base^{exp/2}$$. So what if instead of recursing by exp-1, we recursed by dividing exp in half? Our new complexity could be logarithmic, specifically $$log_2 exp$$.

We have to be careful though about how we handle even and odd exponents (we will need different recursive cases for each):

def power(base, exp):
if exp == 0:
return 1
elif exp == 1:
return base
elif exp % 2 == 0:
sub = power(base, exp//2)
return sub * sub
else:
return base * power(base, exp-1)


Is this implementation still logarithmic? In the worst case the overhead is 2X (two calls for each division), and $$2log_2 n$$ is still logarithmic.