Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
RECURSION RECURSION Objectives: • become familiar with the idea of recursion • Examine examples to show the wide variety of situations to which recursion can be applied. – – – – triangular numbers factorials, recursive binary search, the Towers of Hanoi puzzle. • strengths and weaknesses of recursion RECURSION – A method of programming whereby a function directly or indirectly calls itself – Problems: stop recursion? Designing Algorithms • There is no single recipe for inventing algorithms • There are basic rules: – Understand your problem well – may require much mathematical analysis! – Use existing algorithms (reduction) or algorithmic ideas • There is a single basic algorithmic technique: Divide and Conquer • In its simplest (and most useful) form it is simple induction – In order to solve a problem, solve a similar problem of smaller size • The key conceptual idea: – Think only about how to use the smaller solution to get the larger one – Do not worry about how to solve the smaller problem (it will be solved using an even smaller one) Recursion • A recursive method is a method that contains a call to itself • Technically: – All modern computing languages allow writing methods that call themselves • Conceptually: – This allows programming in a style that reflects divide-nconquer algorithmic thinking – At the beginning recursive programs are confusing – after a while they become clearer than non-recursive variants Recursion Example: Triangular Numbers • 1, 3, 6, 10, 15, 21, … • What is the next member of this series? • The nth term in the series is obtained by adding n to the previous term. • The numbers in this series are called triangular numbers because they can be visualized as a triangular arrangement of objects Recursion int triangle(int n) { if(n==1) return 1; else return( n + triangle(n-1) ); } • The condition that leads to a recursive method returning without making another recursive call is referred to as the base case. • It’s critical that every recursive method have a base case to prevent infinite recursion and the consequent demise of the program. Recursive Methods Must Eventually Terminate A recursive method must have at least one base, or stopping, case. • A base case does not execute a recursive call – stops the recursion • Each successive call to itself must be a "smaller version of itself” – an argument that describes a smaller problem – a base case is eventually reached Key Components of a Recursive Algorithm Design 1. What is a smaller identical problem(s)? l Decomposition 2. How are the answers to smaller problems combined to form the answer to the larger problem? l Composition 3. Which is the smallest problem that can be solved easily (without further decomposition)? l Base/stopping case RECURSION Factorial (N!) • N! = (N-1)! * N [for N > 1] • 1! = 1 • 3! = 2! * 3 = (1! * 2) * 3 =1*2*3 • Recursive design: – Decomposition: (N-1)! – Composition: * N – Base case: 1! factorial Method int factorial(int n) { int fact; if (n > 1) // recursive case (decomposition) fact = factorial(n – 1) * n; // composition else // base case fact = 1; return fact; } int factorial(int 3) { int fact; if (n > 1) fact = factorial(2) * 3; else fact = 1; return fact; } int factorial(int 3) { int fact; if (n > 1) fact = factorial(2) * 3; else fact = 1; return fact; } int factorial(int 2) { int fact; if (n > 1) fact = factorial(1) * 2; else fact = 1; return fact; } int factorial(int 3) { int fact; if (n > 1) fact = factorial(2) * 3; else fact = 1; return fact; } int factorial(int 2) { int fact; if (n > 1) fact = factorial(1) * 2; else fact = 1; return fact; } int factorial(int 1) { int fact; if (n > 1) fact = factorial(n - 1) * n; else fact = 1; return fact; } int factorial(int 3) { int fact; if (n > 1) fact = factorial(2) * 3; else fact = 1; return fact; } int factorial(int 2) { int fact; if (n > 1) fact = factorial(1) * 2; else fact = 1; return fact; } int factorial(int 1) { int fact; if (n > 1) fact = factorial(n - 1) * n; else fact = 1; return 1; } int factorial(int 3) { int fact; if (n > 1) fact = factorial(2) * 3; else fact = 1; return fact; } int factorial(int 2) { int fact; if (n > 1) fact = 1 * 2; else fact = 1; return fact; } int factorial(int 1) { int fact; if (n > 1) fact = factorial(n - 1) * n; else fact = 1; return 1; } int factorial(int 3) { int fact; if (n > 1) fact = factorial(2) * 3; else fact = 1; return fact; } int factorial(int 2) { int fact; if (n > 1) fact = 1 * 2; else fact = 1; return 2; } int factorial(int 3) { int fact; if (n > 1) fact = 2 * 3; else fact = 1; return fact; } int factorial(int 2) { int fact; if (n > 1) fact = 1 * 2; else fact = 1; return 2; } int factorial(int 3) { int fact; if (n > 1) fact = 2 * 3; else fact = 1; return 6; } Execution Trace (decomposition) public static int factorial(int n) { int fact; if (n > 1) // recursive case (decomposition) fact = factorial(n – 1) * n; (composition) else // base case fact = 1; return fact; } factorial(4) factorial(3) 4 public static int factorial(int n) { int fact; if (n > 1) // recursive case (decomposition) fact = factorial(n – 1) * n; (composition) else // base case fact = 1; return fact; } Execution Trace (decomposition) factorial(4) factorial(3) factorial(2) 4 3 public static int factorial(int n) { int fact; if (n > 1) // recursive case (decomposition) fact = factorial(n – 1) * n; (composition) else // base case fact = 1; return fact; } Execution Trace (decomposition) factorial(4) factorial(3) factorial(2) factorial(1) 4 3 2 Execution Trace (composition) public static int factorial(int n) { int fact; if (n > 1) // recursive case (decomposition) fact = factorial(n – 1) * n; (composition) else // base case fact = 1; return fact; } factorial(4) * factorial(3) * factorial(2) 3 * factorial(1)->1 4 2 Execution Trace (composition) public static int factorial(int n) { int fact; if (n > 1) // recursive case (decomposition) fact = factorial(n – 1) * n; (composition) else // base case fact = 1; return fact; } factorial(4) * factorial(3) * factorial(2)->2 4 3 Execution Trace (composition) public static int factorial(int n) { int fact; if (n > 1) // recursive case (decomposition) fact = factorial(n – 1) * n; (composition) else // base case fact = 1; return fact; } factorial(4) * factorial(3)->6 4 Execution Trace (composition) public static int factorial(int n) { int fact; if (n > 1) // recursive case (decomposition) fact = factorial(n – 1) * n; (composition) else // base case fact = 1; return fact; } factorial(4)->24 • This shows the pattern of function calls used to evaluate Factorial(5) recursively To evaluate Factorial(5) evaluate 5 * Factorial(4) To evaluate Factorial(4) evaluate 4 * Factorial(3) To evaluate Factorial(3) evaluate 3 * Factorial(2) Factorial(2) is 2 Return 2 Evaluate 3 * 2 Return 6 Evaluate 4 * 6 Return 24 Evaluate 5 * 24 Return 120 Remember: Key to Successful Recursion • if-else statement (or some other branching statement) • Some branches: recursive call – "smaller" arguments or solve "smaller" versions of the same task (decomposition) – Combine the results (composition) [if necessary] • Other branches: no recursive calls – stopping cases or base cases Warning: Infinite Recursion May Cause a Stack Overflow Error • Infinite Recursion – Problem not getting smaller (no/bad decomposition) – Base case exists, but not reachable (bad base case and/or decomposition) – No base case • Stack: keeps track of recursive calls by JVM (OS) – Method begins: add data onto the stack – Method ends: remove data from the stack • Recursion never stops; stack eventually runs out of space – Stack overflow error look up a name in the phone book? One Possible Way - Binary Search Search: middle page = (first page + last page)/2 Go to middle page; If (name is on middle page) done; //this is the base case else if (name is alphabetically before middle page) last page = middle page //redefine search area to front half Search //same process on reduced number of pages else //name must be after middle page first page = middle page //redefine search area to back half Search //same process on reduced number of pages Binary Search Algorithm • Searching a list for a particular value – sequential and binary are two common algorithms • Binary search: – more efficient than sequential – but the list must be sorted first! • Why it is called binary each unsuccessful test for the target value reduces the remaining search list by 1/2. BINARY SEARCH: Recursion private int search(int target, int first, int last) { int location = -1; // not found if (first <= last) // range is not empty { int mid = (first + last)/2; if (target == a[mid]) location = mid; else if (target < a[mid]) // first half location = search(target, first, mid - 1); else //(target > a[mid]) second half location = search(target, mid + 1, last); } return location; } Where is the composition? • If no items – not found (-1) • Else if target is in the middle – middle location • Else – location found by search(first half) or search(second half) Binary vs. Sequential Search • Binary Search – log2N + 1 comparisons (worst case) • Sequential/Linear Search – N comparisons (worst case) • Binary Search is faster but – array is assumed to be sorted beforehand • Faster searching algorithms for “non-sorted arrays” – More sophisticated data structures than arrays – Later Divide-and-Conquer Algorithms • The recursive binary search is an example of the divide-and-conquer approach. • You divide the big problem into two smaller problems and solve each one separately. The solution to each smaller problem is the same: You divide it into two even smaller problems and solve them. The process continues until you get to the base case, which can be solved easily, with no further division into halves. • The divide-and-conquer approach is commonly used with recursion, although, you can also use a non-recursive approach. • A divide-and-conquer approach usually involves a method that contains two recursive calls to itself, one for each half of the problem. • In the binary search, there are two such calls, but only one of them is actually executed. (Which one depends on the value of the key.) • The mergesort, which we’ll see later actually executes both recursive calls (to sort two halves of an array). RECURSION: Hanoi tower RECURSION: Hanoi tower • The Towers of Hanoi is an ancient puzzle consisting of a number of disks placed on three columns, as shown above. • The disks all have different diameters and holes in the middle so they will fit over the columns. All the disks start out on column A. • The object of the puzzle is to transfer all the disks from column A to column C. • Only one disk can be moved at a time, and no disk can be placed on a disk that’s smaller than itself. • The Towers of Hanoi puzzle can be solved recursively by moving all but the bottom disk of a subtree to an intermediate tower, moving the bottom disk to the destination tower, and finally moving the subtree to the destination. The Value of Recursion • Recursion can be used to replace loops. • Recursively defined data structures, like lists, are very well-suited to processing by recursive procedures and functions • A recursive procedure is mathematically more elegant than one using loops. • Sometimes procedures that would be tricky to write using a loop are straightforward using recursion. Recursion: Final Remarks • The trick with recursion is to ensure that each recursive call gets closer to a base case. • Recursion can always be used instead of a loop. (This is a mathematical fact.) In declarative programming languages, like Prolog, there are no loops. There is only recursion. • Recursion is elegant and sometimes very handy, but it is marginally less efficient than a loop, because of the overhead associated with maintaining the stack. Recursive Versus Iterative Methods All recursive algorithms/methods can be rewritten without recursion. • Iterative methods use loops instead of recursion • Iterative methods generally run faster and use less memory--less overhead in keeping track of method calls So When Should You Use Recursion? • Solutions/algorithms for some problems are inherently recursive – iterative implementation could be more complicated • When efficiency is less important – it might make the code easier to understand • Bottom line is about: – Algorithm design – Tradeoff between readability and efficiency Exercise 1: Recursion • Convert number from H10->H2 7 1 2 3 1 2 1 2 1 0 Summary • Recursive call: a method that calls itself • Powerful for algorithm design at times • Recursive algorithm design: • Decomposition (smaller identical problems) • Composition (combine results) • Base case(s) (smallest problem, no recursive calls) • Implementation – Conditional (e.g. if) statements to separate different cases – Avoid infinite recursion • Problem is getting smaller (decomposition) • Base case exists and reachable – Composition could be tricky