460 likes | 610 Views
Round and round recursion: the good, the bad, the ugly, the hidden. ACSE 2006 Talk Troy Vasiga Lecturer, University of Waterloo Director, CCC. Outline. Recursion defined Real-world examples ("The hidden") Benefits ("The good") Examples How it works Pitfalls ("The bad")
E N D
Round and round recursion: the good, the bad, the ugly, the hidden ACSE 2006 Talk Troy Vasiga Lecturer, University of Waterloo Director, CCC
Outline • Recursion defined • Real-world examples ("The hidden") • Benefits ("The good") • Examples • How it works • Pitfalls ("The bad") • Larger pitfalls ("The ugly") ACSE 2006
Recursion Defined • See "Recursion" ACSE 2006
Real Definition • Recursion is defining a function/procedure/structure using the function/procedure/structure itself in the definition ACSE 2006
A better definition of Recursion • If you still don't understand recursion, see "Recursion" ACSE 2006
A more formal definition • A recursive definition will rely on a recursive definition and some base case(s) • Example: define object X of size n using objects of type X of size k (1 <= k < n) ACSE 2006
Example • Babuska (Russian) dolls ACSE 2006
Real-World Example • Shells ACSE 2006
Real-World Example • Flowers ACSE 2006
Real-World Example • Definition of a human • I was created by my parents • ... who were created by their parents • … (religious/biological discussion follows) ACSE 2006
Using Recursion in CS • Less code implies less errors • Natural way of thinking • mathematical • inductive reasoning • allows both top-down and bottom-up approaches ACSE 2006
(Linked) Lists • C version of linked lists typedef struct list_elem { int val; struct list_elem * next; } node; ACSE 2006
Recursive Ordered Insert void insert(node** head, int newValue) { node* newNode = malloc(sizeof(node)); newNode->val = newValue; newNode->next = NULL; if (*head == NULL) { *head = newNode; } else if ((*head)->next == NULL) { (*head)->next = newNode; } else if ((*head)->next->val > newNode->val) { newNode->next = (*head)->next; (*head)->next = newNode; } else { insert(&(*head)->next, newValue); } } ACSE 2006
Recursive Length int length(node* head) { if (head == NULL) { return 0; } else { return 1+length(head->next); } } ACSE 2006
Scheme lists • In Scheme a list is • the empty list () or • a head element, followed by a list containing the rest of the elements • Examples: • () • (1 2 3 4) • ((a b) (c d) (e f)) ACSE 2006
Scheme lists • How to access elements from a list? • car = head • cdr = tail (rest of the list) • Examples: • (car '(a b c)) => a • (cdr '(a b c)) => (b c) • (car (cdr '(a b c))) => b • (caddr '(a b c)) => c ACSE 2006
Length in Scheme (define reclength (lambda (L) (if (eq? L '()) 0 (+ 1 (reclength (cdr L))) ) ) ) ACSE 2006
Am I right? • Prove it! • Base case: (length '()) => 0 • Assume true for list of length k >= 0 • If length is k+1, our algorithm computes • 1+length(list of size k), which it can do correctly. • Our algorithm is correct. Q.E.D. ACSE 2006
Trees • Extend linked lists in two dimensions • not just "next" but "left" or "right" • Definition • A tree is: • empty, or • is a node which contains • a value • a left tree • a right tree ACSE 2006
Binary Search Trees • In fact, we will insist the following property is also true: • all nodes in the left subtree of a node are less than or equal to the value in the node • all nodes in the right subtree of a node are greater than the value in the node ACSE 2006
9 6 8 4 Picture 10 15 12 23 19 ACSE 2006
Java code public class Node { private int value; private Node left; private Node right; // other methods are straightforward } ACSE 2006
Using trees recursively public void insert(Node root, int newValue) { if (newValue <= root.getValue()) { if (root.getLeft() == null) { root.setLeft(new Node(newValue)); } else { insert(root.getLeft(), newValue); } } else { if (root.getRight() == null) { root.setRight(new Node(newValue)); } else { insert(root.getRight(), newValue); } } } ACSE 2006
Inorder traversal public static void inOrder(Node n) { if (n != null) { inOrder(n.getLeft()); System.out.print(n.getValue()+" "); inOrder(n.getRight()); } } ACSE 2006
Inorder observations • Output on original tree 4 6 8 9 10 12 15 19 23 • Print out "in" the middle • What if we change the printing part? • Exercise: Try to do this without recursion • (Answer: It is really nasty.) ACSE 2006
Preorder traversal public void preOrder(Node n) { if (n != null) { System.out.print(n.getValue()+" "); preOrder(n.getLeft()); preOrder(n.getRight()); } } ACSE 2006
+ / - 4 * 10 5 2 7 Using traversals • Arithmetic expression trees • internal nodes are operators • leave nodes are operands ACSE 2006
Using traversals • Notice the inorder traversal gives: 2 * 7 - 4 + 10 / 5 • Notice the preorder traversal gives: + - * 2 7 4 / 10 5 • TI, anyone? ACSE 2006
Recursion always works • Theorem: Every iterative loop can be rewritten as a recursive call ACSE 2006
How recursion "works" • Implicit call stack • Every call to a function places an "activation" record on top of the stack in RAM • Activation record remember the current state • Stack is built up, and is empty when we return to the main caller ACSE 2006
Avoiding Recursion is Ugly • Consider quicksort • Sorts an array into increasing order ACSE 2006
Recursive Quicksort • Recursive void quicksort (int[] a, int lo, int hi) { int i=lo, j=hi, h; int x=a[(lo+hi)/2]; do { while (a[i]<x) i++; while (a[j]>x) j--; if (i<=j) { h=a[i]; a[i]=a[j]; a[j]=h; i++; j--; } } while (i<=j); if (lo<j) quicksort(a, lo, j); if (i<hi) quicksort(a, i, hi); } ACSE 2006
Iterative Quicksort QuickSort(A,First,Last) { var v,sp,L,L2,p,r,r2; sp=0; Push(First,Last); while( sp > 0 ) { Pop(L, r)/2; while( L < r) { p = (L+r)/2; v = A[p].key; L2 = L; r2 = r; while( L2 < r2 ) { while( A[L2].key < v ) { L2 = L2+L; } // ... ACSE 2006
More iterative Quicksort while( A[r2].key > v ) { r2 = r2-L; } if(L2 <= r2 ) { if(L2 equals r2) { Swap(A[L2],A[r2]); } L2 = L2 + L; r2 = r2 - L; } } // ... ACSE 2006
Yet more iterative Quicksort if(r2-L > r-L2) { if(L<r2) { Push(L,r2); } L = L2; } else { if(L2 < r) { Push(L2,r); r = r2; } } } // end while( L < r ) } // end while( sp>0 ) } // end QuickSort ACSE 2006
Bad Fibonacci, Bad • f(0) = 0 • f(1) = 1 • f(n) = f(n-1) + f(n-2) ACSE 2006
Dynamic Programming • Recursion leads to a very powerful problem solving technique called Dynamic Programming • Essentially, don't use the recursive call, but write a recurrence relation and solve it from the bottom-up ACSE 2006
Shortest Paths using DP • A shortest path is a path between two vertices that has minimum weight • Number the vertices of the graph from 1..n • Let D(k, i, j) mean the shortest path between vertices i and j that uses only vertices 1, …, k ACSE 2006
Base case • D(0, i, j) = • 0 if i=j • w(i,j) if there is an edge (i,j) • otherwise ACSE 2006
Recursive case • D(k+1, i, j) uses either vertex k+1 or it doesn't • If it doesn't, • D(k+1, i, j) = D(k,i,j) • If it does, • D(k+1, i, j) = D(k, i, k+1) + D(k, k+1, j) • So, D(k+1,i,j) is the minimum of these two ACSE 2006
Filling in the table • In fact, filling in the table is done without using recursion • As we did in the Fibonacci case, except now, we are in more than one dimension ACSE 2006
Ugly: Recursive Main • Don't do this in Java public class RecUgly { public static int factorial(int n) { if (n <= 0) { return 1; } else { return n*factorial(n-1); } } public static void main(String[] args) { System.out.println(factorial(11)); } } ACSE 2006
Context-Free Grammars • Describe very complex things using recursion S -> (S) S -> SS S -> ACSE 2006
Mathematical beauty • Koch curves using Lindenmayer systems • Let F mean "forward", + mean "right" and - mean "left" • Koch curve • Rule: F -> F-F++F-F (starting with F) • Koch snowflake • Start with F++F++F instead! ACSE 2006
Dragon curve • X -> X+YF+, Y->-FX-Y (start with FX) ACSE 2006
Conclusion • Recursion is • Beautiful • Natural (in many senses) • Mathematically precise • Simple • Powerful • Recursive ACSE 2006