daily puzzle - · pdf filethe josephus problem
TRANSCRIPT
Lecture 24The mysteries of recursion
Daily Puzzle
Recursion
• The process of recursion may have had its birth in literature. For example, the poem “Sacred Emily”, by Gertrude Stein (1913):
A rose is a rose is a rose
is a rose.
The Droste Effect
• A Dutch term for a specific kind of recursive picture in which an image depicts a smaller version of itself in a place where a similar picture would realistically be expected to appear.
Recursion in Nature
• Structures in nature that contain substructures with the same form as the whole exhibit something known as “self-similarity”.
Visual recursion
• Recursion is used to generate fractals
“a rough or fragmented geometric shape that can be split into parts, each of which is (at least approximately) a reduced-size copy of the whole”.
Hilbert curves
• A continuous fractal space-filling curve first described by the German mathematician David Hilbert (1862-1943) in 1891.
Hilbert curves
Koch snowflake
• One of the earliest fractals, derived from the Koch curve of 1904 by Swedish mathematician Helge von Koch.
Koch snowflake
Cesaro curve, based on Koch infrastructure
Recursion
• Recursion is a programming technique that naturally implements the divide-and-conquer programming methodology.
Barron, in his monograph entitled “Recursive Techniques in
Programming” (1968) said:
“if computers had existed in the Middle Ages, programmers would have been burned at the stake by
other programmers for heresy”
...“it is certain that one of the main heresies would have
been a belief (or disbelief) in recursion”
How is it performed?
• The notion of recursion is linked to the idea of a stack.
• Edsger Dijkstra first described this in 1960.
A STACK WORKS LIKE A PEZ DISPENSER
LIFO - Last-In, First-Out
Items are added by being pushed onto the stack, and
removed by being popped off the stack
A STACK FOR A RUNNING C
PROGRAM USING A RECURSIVE
FUNCTION X.
The stack is used to store a stack frame for each function
that has been called.
static variables
main()
function X called from main()
function X_1 called from X
function X_2 called from X_1
current stack frame
Note that stacks have finite storage.
How does it work?
if (this is a simple case) solve itelse redefine the problem using recursionend if
! Calculating xn
! e.g. x5 = x * x * x * x * x
! and is broken down as:
! x * (x * x * x * x)! x * (x * (x * x * x))! x * (x * (x * (x * x)))! x * (x * (x * (x * (x))))
Is recursion useful?
• Depends on the problem being solved.
• Is it a recursive problem?
• If not, does a recursive solution offer simplicity, or efficiency?
Many types of recursion
• Linear recursion.
• Binary recursion.
• Nested recursion.
• Mutual recursion.
Is recursion good?
• Yes, but not always.
• Some recursive solutions are ubiquitous... - but horrible.
• Don’t use recursion just for the sake of using recursion.
Fibonacci
• Classic, yet “naïve” binary recursive form of deriving Fibonacci numbers.
• Used in nearly every textbook containing recursion.
• It works but at what cost?
Version of Fibonacci algorithm in C:
int fib(int n){ if (n <= 2) return 1; else return fib(n-1) + fib(n-2);}
fib(5)
fib(4) fib(3)
fib(3) fib(2) fib(2) fib(1)
1fib(2) fib(1)
1
1 1
1
+
+
+
+
Fibonacci
• The algorithm grows in exponential time.
• Calculating fib(200) requires approximately 2140 operations.
• Biggest problem is time spent recalculating already calculated numbers.
Fibonacci
Put into perspective:
Fujitsu K computer @ 10.51 petaflops10.51×1015 floating-point ops / sec
2140 = 1.3×1026 seconds≈ 4.2×1016 millennia
Fibonacci
Calculating fib(40):fib(35) calculated 8 times, and
fib(1 or 2) calculated 102,334,155 times.
Total = 204,668,309 function calls
A better way - linear recursion?
int fib(int a, int b, int n){ if (n <= 2) return b; else if (n > 2) return fib(b,a+b,n-1);}
called using f = fib(1,1,n)
for n=40 39 function calls total
0 seconds
A mutually recursive way
for n=40 120 seconds
int fib_babies(int i){ if (i == 1) return 1; else return fib_adults(i-1);}
int fib_adults(int i){ if (i == 1) return 0; else return fib_adults(i-1) + fib_babies(i-1);}
Rubio and Pajak
called using f = fib_adults(n) + fib_babies(n);
A quick recursive way
for n=40 63 recursive calls,
0 seconds
Dijkstra / Shortt
long long fib_DijkstraR(int n){! long long i,j,tempi,tempj;! if (n == 0)! ! return 0;! if (n == 1)! ! return 1;
! if (n%2 == 0){ //even! ! i = (n-1) / 2;! ! j = n / 2;! ! tempi = fib_DijkstraR(i);! ! tempj = fib_DijkstraR(j);! ! return (2 * tempi + tempj) * tempj;! }! else { //odd! ! i = n / 2;! ! j = (n+1) / 2;! ! tempi = fib_DijkstraR(i);! ! tempj = fib_DijkstraR(j);! ! return tempi * tempi + tempj * tempj;! }}
A quick recursive way
CAREFUL!!
Dijkstra / Shortt
tempi = fib_DijkstraR(i);tempj = fib_DijkstraR(j);return (2 * tempi + tempj) * tempj;
DO THIS
return (2 * fib_DijkstraR(i) + fib_DijkstraR(j)) * fib_DijkstraR(j);
NOT THIS!
Towers of Hanoi
• A classic CS example of recursion.
• Simple to implement...harder to conceptualize.
The puzzle was invented by the French mathematician
François Édouard Anatole Lucas (1842-1891) in 1883.
TofH recursive algorithm in C
void hanoiR(int N, int from, int to, int using){ if (N > 0) { hanoiR(N-1, from, using, to); printf("move %d -> %d\n", from, to); hanoiR(N-1, using, to, from); }}
called using hanoiR(3, 1, 3, 2);
hanoiR(3, 1, 3, 2)!! hanoiR(2, 1, 2, 3) ! ! hanoiR(1, 1, 3, 2)! ! ! hanoiR(0, 1, 2, 3) -> recursion terminates! ! move 1 -> 3! ! ! hanoiR(0, 2, 3, 1) -> recursion terminates! ! move 1 -> 2! ! hanoiR(1, 3, 2, 1)! ! ! hanoiR(0, 3, 1, 2) -> recursion terminates! ! ! move 3 -> 2! ! ! hanoiR(0, 1, 2, 3) -> recursion terminates! move 1 -> 3! hanoiR(2, 2, 3, 1)! ! hanoiR(1, 2, 1, 3)! ! ! hanoiR(0, 2, 3, 1) -> recursion terminates! ! ! move 2 -> 1! ! ! hanoiR(0, 3, 1, 2) -> recursion terminates! ! move 2 -> 3! ! hanoiR(1, 1, 3, 2)! ! ! hanoiR(0, 1, 2, 3) -> recursion terminates! ! ! move 1 -> 3! ! ! hanoiR(0, 2, 3, 1) -> recursion terminates
Iterative approaches?
• Using stacks.
• Using 2D arrays.
• A *lot* more code, and algorithm complexity.
#include <stdio.h>
int disk[3][3] = {{1,0,0},{1,0,0},{1,0,0}};
void print_tower(){ printf("%d %d %d\n", disk[0][0], disk[0][1], disk[0][2]); printf("%d %d %d\n", disk[1][0], disk[1][1], disk[1][2]); printf("%d %d %d\n", disk[2][0], disk[2][1], disk[2][2]); printf("\n");}
// Function to determine if all the disks have been movedint is_moved(void){ if (disk[0][2] == 1 && disk[1][2] == 1 && disk[2][2] == 1) return 1; else { return 0; }}
// Function to determine the index, moving counter-clockwiseint ccw(int index){ if (index == 0) return 2; else return index - 1;}
// Function to determine the tower position of the smallest// diskint find_smallest_disk(void){ int disc; if (disk[0][0] == 1) disc = 0; else if (disk[0][1] == 1) disc = 1; else if (disk[0][2] == 1) disc = 2; return disc;}
// Function to return the tower position of the next disk to move.
void find_next_disk(int *disc, int *tower){
int i,j,k,found=1,df,su,disc_pos;
i = 1; while (i <= 2) {
// Check each disc row below the top to find a disc to move j = 0; df = 0; while (j <= 2) { if (disk[i][j] == 1) { df = 1; disc_pos = j; break; } else j = j + 1; } // Check the disc to make sure it can be moved
su = 0; for (k=i-1; k>=0; k=k-1) su = su + disk[k][disc_pos];
if (su == 0) { *disc = i; *tower = disc_pos; break; } else i = i + 1; }}
// Function to move the disk
void move_disk(int disc, int from, int to){ disk[disc][from] = 0; disk[disc][to] = 1;}
int main (void){ int smallest, d, disc, tower;
smallest = find_smallest_disk(); d = ccw(smallest); move_disk(0,smallest,d);
while (!is_moved()) { print_tower();
find_next_disk(&disc, &tower); if (disc == 1){ d = ccw(tower); d = ccw(d); } else if (disc == 2) d = ccw(tower);//printf("%d %d %d\n", disc, tower, d);
move_disk(disc,tower,d); print_tower();
smallest = find_smallest_disk(); d = ccw(smallest); move_disk(0,smallest,d); } print_tower();
return 0;}
Many other algorithms
• Mergesort, Quicksort.
• Greatest common divisor.
• Factorial.
• Palindrome, 8-Queens.
• Maze searching,
• Solving Sudoku.
Ackermann’s Function
• Originated in 1928.
• Used extensively for studies in computational efficiency.
• Highly recursive.
• But - few real applications.
Ackermann’s Function
Ackermann’s Function
A(1,2)= A(0, A(1,1))= A(0, A(0, A(1,0)))= A(0, A(0, 2))= A(0, 3)= 4
Ackermann’s Function
! int ackermann(int m, int n)! {! ! if (m == 0) ! return(n+1);! ! else if (n == 0) ! return(ackermann(m-1,1));! ! else ! return(ackermann(m-1,ackermann(m,n-1)));! } !
nested recursion
Ackermann’s Function
• Can be used to illustrate recursive depth.
• Ackermann(2,2) produces the following recursive structure...
A(2,2)A(1,A(2,1))A(1,A(1,A(2,0)))A(1,A(1,A(1,1)))A(1,A(1,A(0,A(1,0))))A(1,A(1,A(0,A(0,1))))A(1,A(1,A(0,2)))A(1,A(1,3))A(1,A(0,A(1,2)))A(1,A(0,A(0,A(1,1))))A(1,A(0,A(0,A(0,A(1,0)))))A(1,A(0,A(0,A(0,A(0,1)))))A(1,A(0,A(0,A(0,2))))A(1,A(0,A(0,3)))A(1,A(0,4))A(1,5)A(0,A(1,4))A(0,A(0,A(1,3)))A(0,A(0,A(0,A(1,2))))A(0,A(0,A(0,A(0,A(1,1)))))A(0,A(0,A(0,A(0,A(0,A(1,0))))))A(0,A(0,A(0,A(0,A(0,A(0,1))))))A(0,A(0,A(0,A(0,A(0,2)))))A(0,A(0,A(0,A(0,3))))A(0,A(0,A(0,4)))A(0,A(0,5))A(0,6)7
Ackermann’s Function
• Cannot be computed with only definite iteration (a completely defined for loop for example).
• Can be solved using a complex stack.
Ackermann’s Function
Ackermann(4,1)=6553331.85 s
Nonrecursive algorithm using a stack 91.17 s
Pitfalls of recursion
• No guarantee of convergence.
• Excessive space requirements.
• Excessive re-computation.
• Laborious debugging.
Recursion and problem solving
• Look at various approaches to solving a problem.
• Is recursion the best solution?
The Josephus Problem
• Originates from Roman historian Flavius Josephus (37-100).
• Basically a “reduction using a step of size k” problem.
The Josephus Problem
• A group of n people are standing in a circle, numbered consecutively clockwise from 1 to n.
• Starting with the first person, every kth person is removed, proceeding clockwise.
• What is the position of the remaining survivor?
The Josephus Problem
If n = 6, and k=2, then people are removed in the following order:
2, 4, 6, 3, 1
the last person remaining is no. 5.
The Josephus Problem
➊
➋
➌
➍
➎
➏ ×
×
×
×
×
➊
➋
➌
➍
➎
➏
➊
➌
➍
➎
➏
➊
➌➎
➏
➊
➌➎
➊
➎ ➎
×
×
×
×
×
The Josephus Problem
• What’s the best algorithm to use?
The Josephus Problem
• The structure is circular, so a circular array, or linked list is the easiest.
• The problem can also be solved recursively... it’s just trickier.
The Josephus Problem
!int josephus(int n)!{!! if (n == 1)!!! return 1;!! if (n % 2 == 0)!!! return 2 * josephus(n / 2) - 1;!! else!!! return 2 * josephus(n / 2) + 1;!}
works for removing every 2nd person.
Car Parking
Car Parking
void park(double lower, double upper){ double p; p = drand_RANGE(lower,upper); num = num + 1; printf("Car parked at position %.1f\n", p); if ((p-0.5)-(lower-0.5) >= 1.0) park(lower,p-1.0); if ((upper+0.5)-(p+0.5) >= 1.0) park(p+1.0,upper);}
park(0.5,9.5);
initial c
all
Car Parking
double drand_RANGE(double low, double high){ int randomNum; double drand, span;! randomNum = (double)rand(); drand = randomNum / (double) RAND_MAX; span = high - low; return low + span * drand;}
derive random numbers between low and high