The Art of Data Structures Recursionrsarkis/csc162/_static/lectures/Recursion.pdf · What Is...
Transcript of The Art of Data Structures Recursionrsarkis/csc162/_static/lectures/Recursion.pdf · What Is...
The Art of Data Structures Recursion
Richard E Sarkis CSC 162: The Art of Data Structures
Class Administrivia
Agenda
• To understand that complex, difficult problems that may have a simple recursive solutions
• To learn how to formulate programs recursively
• To understand and apply the three laws of recursion
• To understand recursion as a form of iteration
• To implement the recursive formulation of a problem
• To understand how recursion is implemented by a computer system
Recursion
Recursion
• A description of something that refers to itself is called a recursive definition
Recursion
• In mathematics, recursion is frequently used
• The most common example is the factorial:
For example, 5! = 5(4)(3)(2)(1)
Recursion
• In other words,
• Or
• This definition says that 0! is 1, while the factorial of any other number is that number times the factorial of one less than that number
n! = n(n � 1)!
n! =
�1 if n = 0n(n � 1)! otherwise
Recursion
• Our definition is recursive, but definitely not circular.
Consider 4!
• 4! = 4(4-1)! = 4(3!)
• What is 3!? We apply the definition again
4! = 4(3!) = 4[3(3-1)!] = 4(3)(2!)
4! = 4(3!) = 4(3)(2!) = 4(3)(2)(1!) = 4(3)(2)(1)(0!) = 4(3)(2)(1)(1) = 24
Recursion
• Factorial is not circular because we eventually get
to 0!, whose definition does not rely on the
definition of factorial and is just 1
• This is called a base case for the recursion
• When the base case is encountered, we get a closed expression that can be directly (often, trivially) computed
Recursion Numbers Sum
• Suppose that you want to calculate the sum of a list of number, e.g. [1, 3, 5, 7, 9]
Recursion Numbers Sum: Iterative
def list_sum(num_list): the_sum = 0 for i in num_list: the_sum = the_sum + i return the_sum
print(list_sum([1,3,5,7,9]))
Recursion Numbers Sum
• total = (1+(3+(5+(7+9)))) total = (1+(3+(5+16))) total = (1+(3+21)) total = (1 + 24) total = 25
• listSum(numList) = first(numList) + listSum(rest(numList))
Recursion Numbers Sum: Recursive
def list_sum_rec(num_list): if len(num_list) == 1: return num_list[0] else: return num_list[0] + list_sum_rec(num_list[1:])
print(list_sum_rec([1,3,5,7,9]))
Recursion
• This is inefficient (lots of copying), but Python runs out of stack to keep track of the calls before the cost gets out of hand
• Note that the two implementations actually sum the elements in opposite order
• We could make them do it in the same order like this:
Recursion Numbers Sum: Recursive (better)
def listsum_rec2(the_sum, l): if len(l) == 0: return the_sum return listsum_rec2(the_sum + l[0], l[1:])
print(listsum_rec2(0, [1,3,5,7,9]))
Recursion The Recursive Sum Function
def helper(sum, l): if len(l) == 0: return sum return helper(sum + l[0], l[1:])
def listsum_rec3(l): return helper(0, l)
Recursion Recursive Calls Adding a List of Numbers
ObjectivesWhat Is Recursion?
Stack Frames: Implementing RecursionComplex Recursive Problems
Summary
Calculating the Sum of a List of NumbersThe Three Laws of RecursionConverting an Integer to a String in Any Base
Series of Recursive Calls Adding a List of Numbers
sum(1,3,5,7,9) 1 +=
sum(3,5,7,9) 3 +=
sum(5,7,9) 5 +=
sum(7,9) 7 +=
sum(9) 9=
Recursion
Recursion Recursive Calls Adding a List of Numbers
ObjectivesWhat Is Recursion?
Stack Frames: Implementing RecursionComplex Recursive Problems
Summary
Calculating the Sum of a List of NumbersThe Three Laws of RecursionConverting an Integer to a String in Any Base
Series of Recursive Returns from Adding a List ofNumbers
sum(1,3,5,7,9) 1 + 24=
sum(3,5,7,9) 3 + 21=
sum(5,7,9) 5 + 16=
sum(7,9) 7 + 9=
sum(9) 9=
25 =
Recursion
Recursion The Three Laws
1. A robot may not injure a human being or, through inaction, allow a human being to come to harm.
2. A robot must obey the orders given it by human beings except where such orders would conflict with the First Law.
3. A robot must protect its own existence as long as such protection does not conflict with the First or Second Laws.
Recursion Still, they're important laws.
Recursion The Three Laws of…Recursion
1. A recursive algorithm must have a base case
2. A recursive algorithm must change its state and move toward the base case
3. A recursive algorithm must call itself, recursively
Recursion Areas of Use
• Two fundamental computational concepts
• Divide & Conquer: Solve a problem in terms of a smaller version of itself
• Backtracking: Systematically explore a set of possible solutions
Factorial
Factorial
• We’ve seen previously that fact can be calculated using a loop accumulator
• If fact is written recursively…
Factorial
def fact(n): if n == 0: return 1 else: return n * fact(n-1)
Factorial
• We’ve written a function that calls itself, a recursive function
• The function first checks to see if we’re at the base case (n==0). If so, return 1
• Otherwise, return the result of multiplying n by the factorial of n-1, fact(n-1)
Factorial
>>> fact(4)24>>> fact(10)3628800>>> fact(100)93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L>>>
• Remember that each call to a function starts that function anew, with its own copies of local variables and parameters
Factorial13.2. Recursive Problem-solving 435
n = 1
11n =
0
def fact(n): if n == 0: return 1 else: return n * fact(n1)
n:
def fact(n): if n == 0: return 1 else: return n * fact(n1)
n:2 1
def fact(n): if n == 0: return 1 else: return n * fact(n1)
n: 0
fact(5) n = 4
24
n = 3n =
5def fact(n): if n == 0: return 1 else: return n * fact(n1)
n:
def fact(n): if n == 0: return 1 else: return n * fact(n1)
n: 4
def fact(n): if n == 0: return 1 else: return n * fact(n1)
n: 3
2
120
n = 2
6
Figure 13.1: Recursive computation of 5!
13.2.3 Example: String Reversal
Python lists have a built-in method that can be used to reverse the list. Sup-pose that you want to compute the reverse of a string. One way to handle thisproblem effectively would be to convert the string into a list of characters, re-verse the list, and turn the list back into a string. Using recursion, however, wecan easily write a function that computes the reverse directly, without having todetour through a list representation.
The basic idea is to think of a string as a recursive object; a large string iscomposed out of smaller objects, which are also strings. In fact, one very handyway to divide up virtually any sequence is to think of it as a single first item thatjust happens to be followed by another sequence. In the case of a string, we candivide it up into its first character and “all the rest.” If we reverse the rest of thestring and then put the first character on the end of that, we’ll have the reverseof the whole string.
Let’s code up that algorithm and see what happens.
def reverse(s):
return reverse(s[1:]) + s[0]
Notice how this function works. The slice s[1:] gives all but the first characterof the string. We reverse the slice (recursively) and then concatenate the first
Fibonacci
Fibonacci
• Sometimes one has to be careful with recursion
• In addition to limits on stack depth [sys.setrecursionlimit(limit)], and the cost of argument copying, some naive recursive algorithms are inherently expensive
Fibonacci Iterative Fibonacci function – O(n)
def fib_iter(n): a = 0 b = 1 i = 0 print(a) while i < n: t = a+b a = b b = t print(a) i += 1
return a
Fibonacci Naive recursive version, O(2n)
def fib_rec_1(n): if n < 2: return 1 return fib_rec_1(n-1) + fib_rec1(n-2)
Fibonacci Good recursive version, O(n)
def fib_rec_2(n): def helper(a, b, i): if i == n: return b return helper(b, a + b, i + 1)
return helper(1, 0, 0)
Recursion Exercise
CSC 161Prof. R Sarkis
QuizAlgorithms
University of RochesterSpring 2017
Keep your quiz until instructed to pass it forward!
Student’s Name:
Workshop Leader:
1. Take this recursive function:
1 def fun(n):2 if n == 0:3 return []45 return fun(n//2) + [n%2]67 fun(25)
(a) Identify the line number for the base case in fun(n):
(b) Identify the line number, and specific recursive call in fun(n):
(c) Use the table below to determine the output of fun(n) when it is called with n = 25 (line 7):
RecursionLevel
n fun(n) iscalled with. . .
is n == 0? line 5, calculaten//2
line 5, calculaten%2
0 (start) 25 fun(25) FALSE 12 11 122345
(d) The final returned value from calling fun(25):
1 def fun(25): 2 if n == 0: 3 return [] 4 5 return fun(25//2) + [25%2]
1 def fun(12): 2 if n == 0: 3 return [] 4 5 return fun(12//2) + [12%2]
1 def fun(6): 2 if n == 0: 3 return [] 4 5 return fun(6//2) + [6%2]
1 def fun(3): 2 if n == 0: 3 return [] 4 5 return fun(3//2) + [3%2]
1 def fun(1): 2 if n == 0: 3 return [] 4 5 return fun(1//2) + [1%2]
1 def fun(0): 2 if n == 0: 3 return [] 4 5 return fun(n//2) + [n%2]
fun(25)
[]
[] + [1]
[] + [1] + [1]
[] + [1] + [1] + [0]
[] + [1] + [1] + [0] + [0] + [1]
[1,1,0,0,1]
[] + [1] + [1] + [0] + [0]
[1]
[1, 1]
[1, 1, 0]
[1, 1, 0, 0]
[1, 1, 0, 0, 1]
recursion level n fun(n) is
called with… is n == 0? line 5, calc n//2
line 5, calc n%2
0 (start) 25 fun(25) FALSE 12 1
1 12 fun(12) FALSE 6 0
2 6 fun(6) FALSE 3 0
3 3 fun(3) FALSE 1 1
4 1 fun(1) FALSE 0 1
5 0 fun(0) TRUE nothing, it returns
nothing, it returns
Int → Str (in any base)
Int → Str (in any base)
• Reduce the original number to a series of single-digit numbers
• Convert the single digit-number to a string using a lookup
• Concatenate the single-digit strings together to form the final result
Int → Str in Base 10
ObjectivesWhat Is Recursion?
Stack Frames: Implementing RecursionComplex Recursive Problems
Summary
Calculating the Sum of a List of NumbersThe Three Laws of RecursionConverting an Integer to a String in Any Base
Converting an Integer to a String in Base 10
toStr(769) 769 / 10 ‘9'
toStr(76) 76 / 10 ‘6'
toStr(7) 7 < 10 ‘7'
Remainder
+
+
Recursion
Int → Str in Base 2–16
def to_str(n, base): convert_string = "0123456789ABCDEF"
if n < base: return convert_string[n] else: return to_str(n // base, base) + convert_string[n % base]
print(to_str(1453, 16))
Int → Str Decimal 10 to its Binary String
ObjectivesWhat Is Recursion?
Stack Frames: Implementing RecursionComplex Recursive Problems
Summary
Calculating the Sum of a List of NumbersThe Three Laws of RecursionConverting an Integer to a String in Any Base
Converting the Number 10 to its Base 2 StringRepresentation
toStr(10) 10 / 2 ‘0'
toStr(5) 5 / 2 ‘1'
toStr(2) 2 / 2 ‘0'
Remainder
toStr(1) 1 < 2 ‘1'
+
+
+
Recursion
Int → Str Pushing the Strings onto a Stack
r_stack = Stack()
def to_str(n, base): convert_string = "0123456789ABCDEF" if n < base: r_stack.push(convert_string[n]) else: r_stack.push(convert_string[n % base]) to_str(n // base, base)
Int → Str Strings Placed on the Stack
ObjectivesWhat Is Recursion?
Stack Frames: Implementing RecursionComplex Recursive Problems
Summary
Strings Placed on the Stack During Conversion
‘0'
‘1'
‘0'
‘1'
Recursion
Int → Str Call Stack: toStr(10,2)
ObjectivesWhat Is Recursion?
Stack Frames: Implementing RecursionComplex Recursive Problems
Summary
Call Stack Generated from toStr(10,2)
toStr(10,2) n = 10 base = 2
toStr(10/2,2) + convertString[10%2]
toStr(5,2) n = 5 base = 2
toStr(5/2,2) + convertString[5%2]
toStr(2,2) n = 5 base = 2
toStr(2/2,2) + convertString[2%2]
'1'
Recursion
String Permutations Revisit Anagram Tester
String Permutations Brute Force Anagram Testing
def permutation(s, prefix=""): n = len(s) if (n == 0): print(prefix) else: for i in range(n): permutation(s[0:i] + s[i+1:n], prefix + s[i])
permutation("ape")
Euclid's Algorithm Greatest Common Divisor (GCD)
Euclid's Algorithm GCD
• https://en.wikipedia.org/wiki/Greatest_common_divisor
Euclid's Algorithm GCD
def gcd(a, b): if b == 0: return a elif a < b: return gcd(b, a) else: return gcd(a-b, b)
Euclid's Algorithm GCD, Improved
def gcd(a, b): if b == 0: return a else: return gcd(b, a % b)
Euclid's Algorithm Extended GCD
def ext_gcd(x, y): if y == 0: return(x, 1, 0) else: (d, a, b) = ext_gcd(y, x%y) return (d, b, a-(x/y)*b)
ObjectivesWhat Is Recursion?
Stack Frames: Implementing RecursionComplex Recursive Problems
Summary
Tower of HanoiSierpinski TriangleCryptography and Modular Arithmetic
Call Tree for Extended GCD Algorithm
x,y =25,9
(d,a,b) = gcd(9,7)return 1,-4,-3-25/9*4
x,y =9,7
(d,a,b) = gcd(7,2)return 1,-3,1-9/7*-3
x,y =7,2
(d,a,b) = gcd(2,1)return 1,1,0-7/2*1
x,y =2,1
(d,a,b) = gcd(1,1)return 1,0,1-2/1*0
x,y =1,1
(d,a,b) = gcd(1,0)return 1,0,1-1*0
x,y =1,0
return 1,1,0
(1,1,0)
(1,1,0)
(1,0,1)
(1,1,-3)
(1,-3,4)
(1,4,-11)
Recursion
Euclid's Algorithm Extended GCD
Towers of Hanoi A Complex Recursive Problem
Towers of Hanoi Background
• Objective: move N disks from peg A to C can be reduced to three sub problems:
1. Move N-1 disks from peg A to intermediate
peg B
2. Move the largest Disk N from peg A to target
C
3. Move the N-1 parked disks from B to C
Towers of Hanoi Background
• Tower of Hanoi (Wikipedia)
• Tower of Hanoi - 5 disks - 31 moves
Towers of Hanoi An Example Arrangement of Disks
ObjectivesWhat Is Recursion?
Stack Frames: Implementing RecursionComplex Recursive Problems
Summary
Tower of HanoiSierpinski TriangleCryptography and Modular Arithmetic
An Example Arrangement of Disks for the Tower ofHanoi
fromPole withPole toPole
Recursion
Towers of Hanoi An Example Arrangement of Disks
• Move a tower of height-1 to an intermediate pole, using the final pole
• Move the remaining disk to the final pole
• Move the tower of height-1 from the intermediate pole to the final pole using the original pole
Towers of Hanoi Python Code for the Tower of Hanoi
def move_tower(height, from_pole, to_pole, with_pole): if height >= 1: move_tower(height - 1, from_pole, with_pole, to_pole) move_disk(from_pole, to_pole) move_tower(height - 1, with_pole, to_pole, from_pole)
def move_disk(fp,tp): print("moving disk from",fp,"to",tp)
move_tower(3, "A", "B", "C")
Towers of Hanoi An Iterative Version
Interesting secret: there's also an easy iterative solution, but it isn't anywhere near as intuitive
1. On every even-numbered move (starting with zero), move the little disk one pole "clockwise" If the total number of disks is even, the first move should be from from_pole to with_pole; if the total number of disks is odd, the first move should be from from_pole to with_pole
Towers of Hanoi An Iterative Version
2. On every odd-numbered move, make the only legal move not involving the smallest disk (there can be only one)
Towers of Hanoi Python Code for the Tower of Hanoi
def hanoi_iter(height, fromPole, toPole, withPole): if height % 2 == 0: poles = [fromPole, withPole, toPole] else: poles = [fromPole, toPole, withPole] stacks = [range(height, 0, -1), [height], [height]] for i in range(2**height-1): if i % 2 == 0: # move little disk fd = (i//2)%3 td = (i//2+1)%3 else: # move other disk fd = (i//2)%3 td = (i//2+2)%3 if (stacks[fd][len(stacks[fd])-1] > stacks[td][len(stacks[td])-1]): td = (i//2)%3 fd = (i//2+2)%3 stacks[td].append(list(stacks[fd]).pop()) move_disk(poles[fd], poles[td])
The Sierpinski Triangle A Complex Recursive Problem
Sierpinski Triangle Background
• Sierpinski triangle (Wikipedia)
Sierpinski Triangle The Sierpinski Triangle
ObjectivesWhat Is Recursion?
Stack Frames: Implementing RecursionComplex Recursive Problems
Summary
Tower of HanoiSierpinski TriangleCryptography and Modular Arithmetic
The Sierpinski Triangle
Recursion
Sierpinski Triangle Python Code
import turtle
def draw_triangle(points, color, my_turtle): my_turtle.fillcolor(color) my_turtle.up() my_turtle.goto(points[0][0],points[0][1]) my_turtle.down() my_turtle.begin_fill() my_turtle.goto(points[1][0], points[1][1]) my_turtle.goto(points[2][0], points[2][1]) my_turtle.goto(points[0][0], points[0][1]) my_turtle.end_fill()
def get_mid(p1, p2): return ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2)
Sierpinski Triangle Python Code
def sierpinski(points, degree, my_turtle): color_map = ['blue', 'red', 'green', 'white', 'yellow', 'violet', 'orange'] draw_triangle(points, color_map[degree], my_turtle) if degree > 0: sierpinski([points[0], get_mid(points[0], points[1]), get_mid(points[0], points[2])], degree-1, my_turtle) sierpinski([points[1], get_mid(points[0], points[1]), get_mid(points[1], points[2])], degree-1, my_turtle) sierpinski([points[2], get_mid(points[2], points[1]), get_mid(points[0], points[2])], degree-1, my_turtle)
Sierpinski Triangle Python Code
def main(): my_turtle = turtle.Turtle() my_win = turtle.Screen() my_points = [[-100, -50], [0, 100], [100, -50]] sierpinski(my_points, 3, my_turtle) my_win.exitonclick()
Sierpinski Triangle
ObjectivesWhat Is Recursion?
Stack Frames: Implementing RecursionComplex Recursive Problems
Summary
Tower of HanoiSierpinski TriangleCryptography and Modular Arithmetic
Building a Sierpinski Triangle
left top right
left top right
left top right
Recursion
Recursion Summary
Recursion Summary
• All recursive algorithms must have a base case
• A recursive algorithm must change its state and make progress toward the base case
• A recursive algorithm must call itself (recursively); Recursion can take the place of iteration in some cases
Recursion Summary
• Recursive algorithms often map very naturally to a formal expression of the problem you are trying to solve
Recursion Summary
• Recursion doesn't have to be any more expensive than iteration (though it is in Python)
• It's definitely more expressive: iteration can't capture recursion in the general case without an explicit stack
Questions?