Recursion Chapter 4 Self-Reference Recursive Definitions Inductive Proofs Implementing Recursion
Imperative Algorithms Based on a basic abstract machine model - linear execution model - storage - control structures von Neumann architecture // Factorial public int fac1 (int i) { int result = 1; for (int j=i; j>1; j--) result *= j; return result; } i result j schmiedecke 09 In2-2-Algor.Paradigms 2
Applicative Algorithms Based on the principle of inductive definition Evaluation pattern taken from mathematical function evaluation Recursion is a basic step f(1) = 1 f(n) = n*f(n-1) // Factorial recursive public int fac1 (int i) { if (i<2) return 1; return i*fac1(i-1); } schmiedecke 09 In2-2-Algor.Paradigms 3
Applicative Algorithms Idea is to apply a function to an argument so you need a function definition like f(1) = 1 f(n) = n*f(n-1) and a method for evaluating that function for a given argument: - Replace variable(s) by arguments - Replace left hand side by right hand side - Repeat until termination So evaluation is described as a set of rewriting rules. schmiedecke 09 In2-2-Algor.Paradigms 4
More Formally An applicative algorithm is a list of functions f 0 (v 01,...,v 0n ) = t 0 (v 01,...,v 0n )... f m (v m1,...,v mn ) = t m (v m1,...,v mn ) function name function parameters function expression A function evaluation for a given set of arguments - replace the parameters in the function expression by the corresponding arguments - evaluate all basic operations and functions in the function expression from left to right according to basic priorities The semantics of the applicative algorithm is the evaluation of the first function. schmiedecke 09 In2-2-Algor.Paradigms 5
Notation To make functions conform the algorithm definition, we introduce the conditional operator: Instead of: f(1) = 1 f(n) = n*f(n-1) We write: f(n) = if n=1 then 1 else n*f(n-1) fi In Java, the conditional operator is (?:) int f(int n) { return n==1? 1 : n*f(n-1); } schmiedecke 09 In2-2-Algor.Paradigms 6
The Semantics of Recursive Functions is the successive application of rewriting rules: fac(5) = 5 * fac(4) = 5 * 4 * fac(3) = 5 * 4 * 3 * fac(2) = 5 * 4 * 3 * 2 * fac(1) = 5 * 4 * 3 * 2 * 1 schmiedecke 09 In2-2-Algor.Paradigms 7
The Execution of Recursive Functions uses a stack to save "partial rewritings": call fac(5) push 5, call method call fac(4) push 4, call method call fac(3) push 3, call method call fac(2) push 2, call method call fac(1) push 1, call method base case contin.fac(2 pop, pop, mult(2,1), push 2 contin.fac(3) pop, pop, mult(3,2), push 6 contin.fac(4) pop, pop, mult(6,4), push 24 contin.fac(5) pop, pop, mult(24,5),push 120 schmiedecke 09 In2-2-Algor.Paradigms 8
Recursion is Natural To learn downhill skiing - climb up a few metres and learn to plow a right bend to the bottom - climb up a few metres and learn to plow a left bend to the bottom - take the lift all the way up and combine right and left bends until you reach the bottom Let's apply this technique to the Towers of Hanoi... schmiedecke 09 In2-2-Algor.Paradigms 9
Towers of Hanoi Puzzle invented by Edouard Lucas in 1883: Put an ordered stack of discs from one peg to another, using a third peg, such that all stacks are ordered at all times. source:http://www.cut-the-knot.org/recurrence/hanoi.shtml For a stack of 1: move disc from start to dest For a stack of 2: move disc from start to aux move disc from start to dest move disc from aux to dest Call this: move 2 from start to dest using aux For a stack of 3: move 2 from start to aux using dest move disc from start to dest move 2 from aux to dest using start schmiedecke 09 In2-2-Algor.Paradigms 10
Inductive Aproach A stack of one can be moved directly to the target pole. Apparently, we can move a bigger stack of discs correctly to the target pole if we have an auxiliary pole. We have tested it for 2 and 3. Use this idea to first move the top of the stack to the auxiliary pole leaving the biggest disc behind to be moved directly to the target. move it to the target. Then move the top of the stack back to the origin, leaving the auxiliary pole empty again for the next step.... until the stack size is 1. move the top of the stack is a recursive application of hanoi to a smaller stack! Parameters? schmiedecke 09 In2-2-Algor.Paradigms 11
Express Hanoi in Java public class Hanoi extends Applet { private Tower start, aux, dest; private int size; public Hanoi () { this.size = 8; // replace by reading from ComboBox.. start = new Tower(size); aux = new Tower(0); dest = new Tower(0); } public void start() { move(size, start, dest, aux); } private void move(int number, Tower s, Tower d, Tower a) { if (number==1) { d.add(s.remove()); repaint(); } else { move(n-1,s,a,d); move(1,s,d,a); move(n-1,a,d,s); } } public void paint(graphics g) { start.display(g, size, 50, 10); aux.display(g, size, 100, 10); dest.display(g, size, 150, 10); } } schmiedecke 09 In2-2-Algor.Paradigms 12
Properties of Hanoi Is it correct? Theorem: if a disc is moved to a stack, it is smaller that ist base Inductive proof: Any movement of a partial stack ends with one stack empty (Lemma1) if a disc is moved which is bigger than the top of a stack, it is moved to an empty stack (Lemma2) - true for first step. - true for second step. - if a partial stack of n-1 discs has been moved properly, a bigger disc is moved to an empty stack in the n th step. schmiedecke 09 In2-2-Algor.Paradigms 13
Towers of Hanoi - Complexity schmiedecke 09 In2-2-Algor.Paradigms 14
Complexity Argument schmiedecke 09 In2-2-Algor.Paradigms 15
schmiedecke 09 In2-2-Algor.Paradigms 16
Recursion is Powerful It can be shown that every algorithm that can be specified can be specified recursively! So, recursion has maximum computation potential. In most situations, you can replace recursion by iteration. Can you do so for Hanoi? Later we will learn about a certain type of complex recursion which cannot be expressed iteratively (so-called R Gramars). schmiedecke 09 In2-2-Algor.Paradigms 17
Replacing Recursion by Iteration Lets consider two "famous" recusive algorithms: the factorial n! fac(n) = if n=1 then 1 else n*f(n-1) fi and the Fibonacci numbers (rabbit generations) fib(n) = if n<=1 then 1 else fib(n-1)+fib(n-2) schmiedecke 09 In2-2-Algor.Paradigms 18
Replacing Recursion by Iteration the factorial n! fac(n) = if n=1 then 1 else n*f(n-1) fi int fac(int n) { int result = 1; } for (int i=2; i<=n; i++) result *= i; return result; Solution is straightforward: use local variable to store intermediate results otherwise returned by recursive calls schmiedecke 09 In2-2-Algor.Paradigms 19
Replacing Recursion by Iteration and the Fibonacci numbers (rabbit generations) fib(n) = if n<=1 then 1 else fib(n-1)+fib(n-2) int fib(int n) { int current = 1, prev = 0, temp; for (int i=1; i<n; i++) { temp = current; current = current + prev; prev = temp; } return current; Same idea, } use local variables to store values that would result from recursive calls schmiedecke 09 In2-2-Algor.Paradigms 20
The Termination Problem What is the domain of the factorial function? fac(n) = if n=1 then 1 else n*fac(n-1) fi int fac(int n) { int result = 1; for (int i=2; i<=n; i++) result *= i; return result; } Oh dear, the applicative agorithm will run indefinitely for negative n... Generally, algorithms must be suspected of being partial functions, i.e they may not terminate on certain arguments. It may be hard to find out, or even impossible... There are algorithms for which we do not know whether they terminate for all valid input data. (There is one in your lab assignments ) schmiedecke 09 In2-2-Algor.Paradigms 21
Tail Recursion There is a "nice" type of recursion, called tail recursion: Such functions have a direct solution for a fixed set of arguments. For every recursive call, a monotonic modification is made to the parameters moving them "towards" the direct solution arguments. Tail recursion is easily translated into iteration. The termination of tail recursive functions can generally be determined easily (but not always). So, in programming, try to use tail recursion and make it obvious. schmiedecke 09 In2-2-Algor.Paradigms 22
Recursion and Complexity How do you determine the complexity of a recursive function? Use recursive call as basic step. How much does it cost except for the rec. call? How many recursions occur? the number of calls depends on the parameter value. Multipy step cost by recursion depth. Fac is called n times, each step has constant complexity, so the complexity is O(n): fac(n) = if n=1 then 1 else n*f(n-1) fi Fib is called 2*n times, so the complexity is O(n) too: fic(n) = if (n=1 n=2) then 1 else fib(n-1)+fib(n-2) fi Those were easy to assess, because the parameter is decreasing steadily. But what about this function: f(x) = if x>100 then x-10 else f(f(x+11)) fi schmiedecke 09 In2-2-Algor.Paradigms 23
Incredible Growth: The Ackermann Function Here is the worst example ever known: ack(x,y) = if x<=0 then y+1 Some function results else if y<=0 then f(x-1,1) else f(x-1,f(x, y-1)) f(0,0) = 1 f(1,0) = 2 f(1,1) = 3 f(2,2) = 7 f(3,3) = 61 f(4,4) = 2**2**2**2**2**2**2 3 = 2**2**2**65536 3 As this growth is achieved by adding 1, the time consumption, i.e. the functions comlexity, is just as incredible schmiedecke 09 In2-2-Algor.Paradigms 24
Mathematics of Applicative Programming: The Lambda Calculus Inventend by Church and Kleene in the 1930ies. Mathematical model of function application (to arguments) Computability question: Which problems can be solved algorithmically, and can it be decided whether a problem has a solution. Abstract machine for applicative algorithms: If it terminates on a given algorithm, it is said to be computable. schmiedecke 09 In2-2-Algor.Paradigms 25
Lamda Calculus Simplified Lambda stands for argument binding: Function f applied to argument a results r: Lambda x f a r If f is x+2, and a is 7 we get Lambda x x+2 7 9 With nesting: Lambda x x+(lambda y y+3 4) 5 Lambda x x+7 5 12 So, computation is described as rewriting of one list of terms by another schmiedecke 09 In2-2-Algor.Paradigms 26
The LISP language Describing computation as a set of rewriting rules has many offsprings in artificial intelligence. Here is a very early rewriting language, invented in 1958 by John Mc Carthy: LISP List Programming - The idea is that every program is a list of rewriting rules. - The result of a rewriting is again a list. - Terminal symbols can be specified to be excluded from rewriting. - Lambda binding is applied to introduce parameters. - Special list operations cons, car and cdr allow list manipulation and traversion. LISP is still in use implemented in Unix, control language in the EMACS editior. schmiedecke 09 In2-2-Algor.Paradigms 27
(DEFUN THROW-DIE () (+ (RANDOM 6) 1) ) Some LISP (DEFUN THROW-DICE () (LIST (THROW-DIE) (THROW-DIE)) ) (DEFUN BOXCARS-P (A B) (AND (EQUAL '6 A) (EQUAL '6 B) ) ) (DEFUN SNAKE-EYES-P (A B) (AND (EQUAL '1 A) (EQUAL '1 B) ) ) schmiedecke 09 In2-2-Algor.Paradigms 28
Factorial in LISP (DEFUN FAC (N) (COND ((EQUAL N 1) 1) ((MULT N (FAC (MINUS N 1)) ) ) ) Evaluate Call of FAC (3) by Rewriting: FAC (3) MULT 3 (FAC (MINUS 3 1)) MULT 3 (FAC (2)) MULT 3 (MULT 2 (FAC (MINUS 2 1)) MULT 3 (MULT 2 (FAC (1))) MULT 3 (MULT 2 1) MULT 3 2 6 schmiedecke 09 In2-2-Algor.Paradigms 29
What's so Charming about LISP? Fully declarative non-operational. Completely general. Easy to extend programs by adding definitions. Results can be added as (redundant) definitions, such as (DEFUN FAC(3) 6) Supports artifical "learning" schmiedecke 09 In2-2-Algor.Paradigms 30
Recursive Fun: Fractals If you draw a simple shape repeatedly with decreasing size and changing orientation, you get a pattern. The simplest way to do that is recursion: - draw a pattern - spawning a similar pattern of smaller size and modified position and orientation - which again spawns another smaller pattern - until the size is below a certain limit Such patterns are usually called fractals... And they are fun doing schmiedecke 09 In2-2-Algor.Paradigms 31
Two Fractal Classics Pythagoras Tree: - Draw a square starting from ist base line - then, for a given angle, draw a pair of Pythagoras squares opposite to its base line - continue for each sqare until too small. Sierpinski's Triangle: - Take a line between two points - Replace it by three lines of half that length, turning -60, +60,+60 left - Replace each of these lines again by three, turning +60, -60, -60 for the first line, -60, +60, +60 for the middle line, +60, -60, -60 for the last line. - Repeat for each line, changing orientation every time until too small schmiedecke 09 In2-2-Algor.Paradigms 32
Famous simple Fractals Pythagoras Fractal Szerpinsky Triangle schmiedecke 09 In2-2-Algor.Paradigms 33
Pythagoras public void paint (Graphics g) { } g.clearrect(0,0,500,500); painttree(g, double x1, double y1, // tree defined double x2, double y2); // by base line // paint tree paints the major square // determines the base lines of the smaller squares // and calls itself recursively for the two smaller squares schmiedecke 09 In2-2-Algor.Paradigms 34
Everybody loves Mandelbrodt Fractals. Have Fun... schmiedecke 09 In2-2-Algor.Paradigms 35
That's enough for today!! Next time, we will return to recursion to consider the most general problem solver: Recursive Backtracking, Before Jumping into the Tradition of Sorting and Searching