0% found this document useful (0 votes)
15 views99 pages

Ceng2001 Week6

Uploaded by

canseverhicrann
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views99 pages

Ceng2001 Week6

Uploaded by

canseverhicrann
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 99

Recursion

Data Structures and Algorithms


Week 6

Erdem Türk, PhD


Fall 2024
Recursion Definition
Recursion is the process of defining a
problem (or the solution to a problem) in
terms of (a simpler version of) itself.

"Droste effect 1260359-3" by Nevit Dilmen (talk) - Own work. Licensed under CC BY-SA 3.0 via
Wikimedia Commons -
https://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Droste_1260359-
nevit.jpg/1280px-Droste_1260359-nevit.jpg
For example, we can define the operation
"find your way home" as:

If you are at home, stop moving.

Take one step toward home.

"find your way home".

Here the solution to finding your way home is two steps


(three steps). First, we don't go home if we are already
home. Secondly, we do a very simple action that makes
our situation simpler to solve. Finally, we redo the entire
algorithm.
Python example (loop)
• count down from some number n to 1

def countDown(n):
while n > 0:
print(n)
n = n - 1
Python example (recursive)
• count down from some number n to 1

def countDown(n):
print( n )
if n > 1:
countdown( n-1 )
book example: sum a list
• sum list of numbers [ 2, 5, 10 ]
• Think about + works on two operands
• so you can add the first item on list to the sum of
the rest of the list
• i.e. we get this sequence
• sum( [2, 5, 10]) = 2 + sum of [5, 10 ]
• sum( [ 5, 10] ) = 5 + sum( [ 10 ])
• sum( [ 10 ] ) = 10 + sum( [ ] )
• we can have sum of empty list return 0
python book example (modified)
def sum_list(a_list):
if len(a_list) == 0: # empty
return 0
return a_list[0] +
sum_list(a_list[1:])
Three Laws of Recursion
like the robots of Asimov, recursion must follow 3 laws

1. A recursive algorithm must have a


base case (when to stop)
2. A recursive algorithm must move
toward the base case
3. A recursive algorithm must call itself
recursively
visualize it sum a list

def sum_list(a_list):
if len(a_list) == 0: # empty
return 0
item1 = a_list[0]
rest = sum_list(a_list[1:])
return item1 + rest

total = sum_list([ 1, 3, 5, 7 ])
sum([1,3,5,7]
)
= 1+
sum([1,3,5,7]
)
= 1+

sum([3,5,7]) = 3+
sum([1,3,5,7]
)
= 1+

sum([3,5,7]) = 3+

sum([5,7]) = 5+
sum([1,3,5,7]
)
= 1+

sum([3,5,7]) = 3+

sum([5,7]) = 5+

sum([7]) = 7+
sum([1,3,5,7]
)
= 1+

sum([3,5,7]) = 3+

sum([5,7]) = 5+

sum([7]) = 7+

sum([]) = 0
sum([1,3,5,7]
)
= 1+

sum([3,5,7]) = 3+

sum([5,7]) = 5+

sum([7]) = 7+

sum([]) = 0
sum([1,3,5,7]
)
= 1+

sum([3,5,7]) = 3+

sum([5,7]) = 5+

sum([7]) = 7+ 0

sum([]) = 0
sum([1,3,5,7]
)
= 1+

sum([3,5,7]) = 3+

sum([5,7]) = 5+ 7

sum([7]) = 7+ 0

sum([]) = 0
sum([1,3,5,7]
)
= 1+

sum([3,5,7]) = 3 + 12

sum([5,7]) = 5+ 7

sum([7]) = 7+ 0

sum([]) = 0
sum([1,3,5,7]
)
= 1 + 15

sum([3,5,7]) = 3 + 12

sum([5,7]) = 5+ 7

sum([7]) = 7+ 0

sum([]) = 0
sum([1,3,5,7]
)
= 1 + 15 16

sum([3,5,7]) = 3 + 12

sum([5,7]) = 5+ 7

sum([7]) = 7+ 0

sum([]) = 0
Converting a Number to a string
representation of any base
Same problem as in chapter 3 that
used a stack, but we will use
recursion
How we are going to do it
• look at base 10 and the number 769
• suppose we have convStr = '0123456789'
• Its easy to convert any number n less than 10,
it is just convStr[n] – our base case
• Now if we can work toward the base case, by
peeling a number off the original, we can use
recursion
• Well we can use n // 10 and n % 10 to get the
original 769 into 76 and 9
now we can use base
• what works with base 10 will work with base 2
to 16, we just use n // base and n % base to
split off one digit and expand our lookup
string to "0123456789ABCDEF"
the code
def toStr(n,base):
digits = '0123456789ABCDEF'
if n < base:
return digits[n]
return toStr(n // base,base)
+ digits[n % base]
Using recursion to draw things

turtle graphics
what is turtle graphics
• simple drawing for kids
• invented in late 60s
• popular in a OOP language called logo
• Imagine a turtle, that has a tail that can paint
a color in the path of the turtle as it moves.
• It follows very simple commands to turn left
or right a number of degrees
• to raise or lower its tail to draw.
• And you can tell it to move forward so many
steps or backward so many steps
Initial turtle direction and position
+y

-x starts at 0,0 coordinates +x


pointing to right

-y
t.forward(50)
t.right(90)
t.forward(50)
t.color("red")
t.left(45)
t.backward(50)
t.right(90)
t.up()
t.forward(10)
t.down()
t.forward(10)
t.up()
t.forward(10)
t.down()
t.forward(10)
code to set up drawing and close
import turtle

# create turtle named t


t = turtle.Turtle()
win = turtle.Screen() # setup window

t.forward(50) # do all your drawing

# setup to close on click


win.exitonclick()
Controlling the drawing
• Some simple methods for a turtle object named t
t.right(degrees)and t.left(degrees)
t.forward(dist)and t.backward(dist)
t.up() and t.down()
t.color("blue")
t.color(0,128,255) # rgb 0-255
t.color("#8abacc") # web colors
t.width(pensize)
import turtle

def tree(branchLen,t):
if branchLen > 5:
t.forward(branchLen)
t.right(20)
tree(branchLen-15,t) # recurse next branch 15 shorter
t.left(40)
tree(branchLen-15,t) # recurse next branch 15 shorter
t.right(20) # return to start direction
t.backward(branchLen) # back down branch same to start pos

def main():
t = turtle.Turtle()
myWin = turtle.Screen()
t.left(90)
t.up()
t.backward(100)
t.down() # now at base of tree
t.color("green") # set color of tree start
tree(75,t) # draw tree starting at 75 length branches
myWin.exitonclick()

main()
Use the debugger

Step out of – don't step until the return


of current method, especially nice if you
did not need to step into this method

Force Step Into – advanced, don't use

Step Into – step into any method call on this line

Step over – execute this line but do not go into any methods
Sierpinski Triangle

fractal shapes, as you zoom in they


just have the same structure, yet
smaller
Sierpinski Triangle Algorithm:
1. given three points that define the points of a
triangle of equal sides: p1, p2, p3: draw it
2. Now calculate three new points as:
• a= midpoint of p1 and p2
• b= midpoint of p2 and p3
• c = midpoint of p1 and p3
3. repeat step one for each of the following new
triangles as defined as these points
( p1,a,c) and (a,p2,b) and (c, b, p3)
p1

c a

p3 p2
b
p1

c a

p3 p2
b
p1

c a

p3 p2
b
p1

c a

p3 p2
b
Look at code for Siepinski
Triangle
Towers of Hanoi

Edouard Lucas math problem of 1883


Move all the disks to last pole
Note how each disk is a
little smaller than the one above

Sounds easy…. Ah, but there are rules to follow


1. You can only move one ring at a
time, and only to another pole
2. You can never put a bigger ring
on top of a smaller ring
Original problem
• There were 64 rings of gold to move
• This task was given to some ancient order of
monks
• It is said that when they accomplished the
task that their temple would turn to dusk, and
the world would end.
How many moves would 64 disks take?

• 18,446,744,073,709,551,615 which is 264-1


• At one move per second, it would take

584,942,417,355 years (584.9 billion years)


The estimated age of the universe is only
13,820,000,000 ( 13.82 billion years)
A B C

Recursive high level:


picture of poles after step 2

1. Move a tower of heigth-1 to an intermediate


pole using the final pole.
2. Move the remaining disk to the final pole
(bigger than all moved in step 1)
3. Move the tower of height-1 from the
intermediate pole to the final pole using the
original pole.
• check out the code
• it prints the steps to follow to solve the
problem

• check out animation at


http://towersofhanoi.info/Animate.aspx
Solving a Maze

recursive solutions to decision trees


A MAZE (how to solve)
With turtle
simplify, make the use a grid, and walls are
just a solid grid cell (green)
Basic Step
• From where turtle is
1. if you are outside the maze, you have succeeded
2. if you can not move in any direction, you have failed
3. if you can move to north square
then go there and recurse back to step 1
4. if you can move to south square
then go there and recurse back to step 1
5. if you can move to west square
then go there and recurse back to step 1
6. if you can move to east square
then go there and recuse back to step 1
 RULE: You can not move to anyplace you have been (leave
crumb)
def searchFrom(maze, startRow, startColumn):
# try each of four directions from this point until we find a way out.
# base Case return values:

maze.updatePosition(startRow, startColumn) # We hit obstacle, return false

if maze[startRow][startColumn] == OBSTACLE :
return False

if maze[startRow][startColumn] == TRIED
or maze[startRow][startColumn] == DEAD_END:
return False # We have found a square that has already been explored

if maze.isExit(startRow,startColumn): # leave color showing path


maze.updatePosition(startRow, startColumn, PART_OF_PATH)
return True # succeeded
maze.updatePosition(startRow, startColumn, TRIED)

# Otherwise, use logical short circuiting to try each direction


# in turn (if needed)
found = searchFrom(maze, startRow-1, startColumn) or \
searchFrom(maze, startRow+1, startColumn) or \
searchFrom(maze, startRow, startColumn-1) or \
searchFrom(maze, startRow, startColumn+1)
if found:
maze.updatePosition(startRow, startColumn, PART_OF_PATH)
else:
maze.updatePosition(startRow, startColumn, DEAD_END)
return found
input file: maze2 file
++++++++++++++++++++++
+ + ++ ++ +
+ ++++++++++
+ + ++ ++++ +++ ++
+ + + + ++ +++ +
+ ++ ++ + +
+++++ + + ++ + +
+++++ +++ + + ++ +
+ + + S+ + +
+++++ + + + + + +
++++++++++++++++++++++
code to read file
mazeFile = open(mazeFileName,'r')
rowsInMaze = 0
for line in mazeFile: # get next line into string
line = line.rstrip("\r\n") # remove invisibles
rowList = []
col = 0
for ch in line: # process each char in line
rowList.append(ch) # add to list
if ch == 'S': # remember start position
self.startRow = rowsInMaze
self.startCol = col
col = col + 1
rowsInMaze = rowsInMaze + 1
self.mazelist.append(rowList) # add row to maze
columnsInMaze = len(rowList)
special characters in maze array
 blank – empty cell
 + wall
 O part of path (solution path)
 . bread crumb – tried
 - dead end
General backtrack technique
• any maze type problem
• generalized to problems picking different
paths and eventually finding a best path
• chess, checkers, tic tack toe application
• also can be used to find shortest solution
• When return from recursion, we automatically
backtrack
Minimum number of coins to
make change
Introduction to
dynamic programming
optimization problems
• there are many problems in computer science
where you want to optimize some value.
• find the shortest path between two points
• find a line that best fits a set of points
• find the smallest set of objects that meet a
criteria
• choose the fastest set of connections though a
network
Making change optimally
• Suppose you make change for a customer for
63 cents. What is the smallest number of
coins to make the change.

• Answer is 6 coins
2 Quarters + 1 dime + 3 pennies
25 + 25 + 10 + 1 + 1 + 1 = 68
minimum for 63 cents
• This method of starting with the biggest coin
first, use all quarters and then all dimes, and
then all nickels and finally pennies called the
greedy algorithm approach
• Try to solve a big part of the problem right
away. (using the largest coin)
– it works for American coins but what about the
imaginary country: Lower Elbonia where they
have coins for 1, 5, 10, 25 and a special 21 cent
piece.
21
cent
• With the extra 21 cent piece our algorithm will
not work. We would still have a 6 coin solution.
by picking the largest coins: 25 + 25 we would
then have 13 cents left, so next we would pick
the dime, we would have 3 cents left and we
would pick a nickel and three pennies.
• But with the 21 cent piece available we can just
choose to pay with three 21 cent pieces = 63
cents
Our Recursive Solution
• Each recursive step we try each possible coin:
25, 21, 10, 5 and 1
And then pick the one with the minimum number of coins for
the smaller problem:

1 + minChange(amt-1)
1 + minChange(amt-5)
minChange(amt)= min of 1 + minChange(amt-10)
1 + minChange(amt-21)
1 + minChange(amt-25)
code:
def minChange(coinValueList,change):
minCoins = change
if change in coinValueList:
return 1
else:
for i in [c for c in coinValueList if c <= change]:
numCoins = 1 + minChange(coinValueList,change-i)
if numCoins < minCoins:
minCoins = numCoins
return minCoins

print(minChange([1,5,10,25],63))
Why does it take so long
• it turns out this code is very slow. It will take over
a minute to run
• try it, but be patient
• It requires 67,716,925 recursive calls to solve the
63 cent change problem with American money.

Lets look at the top of the call chart for solving for
making 26 cents in change, which takes 377 calls:
Note: blue numbers are the coin value subtracted, number
in circle represents recursive call value

Top of Tree with 377 function calls


note some of the duplicate function calls marked in same colors
How to speed up this solution
• since there are many calls that have the same
argument passed, they end up duplicating the
work over and over again, we will use a form of
caching called memoization:
In computing, memoization is an optimization
technique used primarily to speed up computer
programs by storing the results of expensive
function calls and returning the cached result
when the same inputs occur again. -- Wikipedia
• We use a dictionary and store the argument as a
key and the value will be the result.
• Pseudo code of this idea, supposing we start with
a empty dictionary called cache:
def func( a ):
if a in cache:
return cache[a] # return stored value
else:
calculate value
cache[a] = value # store result in cache
return value # return value calculated
Using Memoization for this problem
• Although this technique really speeds up this
coin problem, you still will have the overhead
of a lot of extra function calls and the
associated memory.

• Lets look at a better way …


dynamic programming
Dynamic Programming
• In recursion solutions we write the code to solve a big
problems by working toward a simpler problem.
• Some times this results in a lot of redundancy of
recursive steps
• Dynamic programming offers a better solution to some
of these problems by starting with a simple problem
and building on these smaller problems to solve the
bigger ones.
• reference:
http://www.cs.yale.edu/homes/aspnes/pinewiki/Dyna
micProgramming.html
How it will work for coins
• this creates a look up table (similar to the
memoization technique) but it does it by
building a table of all the solutions up to the
size of the problem we are solving.

• It is kind of like the opposite philosophy of


recursion:
opposite direction to recursive
• Recursion work from big problem toward
simpler problem and eventually the base case

• Dynamic Programming, you start with the


base case, and solve every problem in
complexity up to the big problem. storing the
solutions along the way and using them for
each new bigger step.
Build our table (coins 1, 5, 10, 25)
solution
for input

step 1

step 2

step 3

step 4

step 5

step 6

step 7
Step 11, each step has to find best
solution for each coin
A penny plus the minimum number of coins to make change for 11−1=10 cents (1)
A nickel plus the minimum number of coins to make change for 11−5=6 cents (2)
A dime plus the minimum number of coins to make change for 11−10=1 cent (1)

table lookup for:


1 2 3 4 5 6 7 8 9 10 11
The code

You might also like