230 likes | 319 Views
Intro to CS – Honors I Recursion. Georgios Portokalidis gportoka@stevens.edu. What is Recursion?. Define a process, data, algorithm, etc. in terms of itself Example in math: the sum of n numbers ∑ n = a 0 + a 1 + … a n or ∑ i = 0 ..n = a i
E N D
Intro to CS – Honors IRecursion Georgios Portokalidis gportoka@stevens.edu
What is Recursion? • Define a process, data, algorithm, etc. in terms of itself • Example in math: the sum of n numbers ∑n = a0 + a1 + … an or ∑i=0..n= ai • Recursively the sum function builds on itself ∑i=0..n = a0 + ∑i=1..n • Example in programming languages: a method can invoke itself like any other method
Algorithms Where Recursion is Useful • Many search algorithms can be easily expressed using recursion • Example: searching for a name in the phonebook • Open the phonebook in the middle • Did we find the name? Lucky! • Is the name alphabetically before that page? Repeat the process with the first half of the phonebook • Is the name alphabetically after that page? Repeat the process with the second half of the phonebook • The last steps involve subtasks that are a small version of the entire algorithm Appropriate for recursion
A Simple Example • Countdown from a number to 1 and then print a newline character • We already know how to do this using loops, but try to use recursion to achieve the same goal • Example: n=3 321\n • How would you complete countdown() using recursion? Not properly ending recursion can result in infinite recursion void countdown(int from) { if (from <= 0) System.out.println(); } Start with the end condition. There must always be an end condition.
Why Recursion is Possible? • Every time we invoke a method it gets a new version of its parameters • The callee (i.e., the method calling) waits until the method called returns • The above two are not different when we use recursion, thus enabling it • But, where are the method arguments stored each time?
Memory Organization of Program • Every program usually has 3 different memory areas to store data • Note that all this areas are in the same memory, the different is in their organization and intended use • The heap is used to store dynamically allocated data (stuff allocated using new), such as object instance variables • The data segment is used to store static variables, like class variables and constants. I.e.,the “globals” of the program • The stack is used to store data related with method invocation, like method arguments and local variables
The Stack • A stack refers to placing one item on top of the other • A stack data structure follows last-in, first-out semantics
Keys to Successful Recursion • A definition of a method that includes a recursive invocation of the method itself will not behave correctly unless you follow some specific design guidelines. The following rules apply to most cases that involve recursion: • The heart of the method definition must be an if-else statement or some other branching statement that leads to different cases, depending on some property of a parameter of the method • One or more of the branches should include a recursive invocation of the method. These recursive invocations must, in some sense, use “smaller” arguments or solve “smaller” versions of the task performed by the method • One or more branches must include no recursive invocations. These branches are the base cases, or stopping cases • One common way to ensure that your stop case is reached is to make recursive invocations of your method use a “smaller” argument
StackOverflowError • Can it be caught? • Should you use it to “fix” bugs in your code? • A StackOverflowError is thrown to notify the user/programmer that the stack was exhausted! • It can be caught, but you can’t fix it! • You can increase the size of the stack the VM is using when launching a program • Example: from the command line java -Xss... • java –Xss4m MyProg
From Recursion to Iterative Execution • Any method definition that includes a recursive call can be rewritten, so that it accomplishes the same task without recursion • Recursion can be wasteful • That is usually accomplished by using a loop • Frequently also some additional variables • A non-recursive repetitive process is called iteration • The method implementing it is called an iterative method void countdown(int from) { for (inti = from; i >= 0; i++) System.out.print(i); System.out.println(); } Writing countdown without recursion
Recursive Methods Returning a Value • The Fibonacci sequence: Fn= Fn-1 + Fn-2, where F0 = 0 and F1 = 1 • Let’s start with the stop cases intfibonacci(int n) { if (n == 0) return 0; if (n == 1) return n; … }
Generic Search • The simplest way to search an array for an item (i.e., check that an item is present) in the array is to iterate over all of its elements • public intfindItem(int[] myArray, int item) • { • int found = -1; • for (inti = 0; i < myArray.length; i++) • { • if (item == myArray[i]) • { • found = i; • break; • } • } • return found; • } Time to complete is linear to the size of the array. If it contains n items, worst case is n comparisons O(n) Not the fastest way
Searching Over Sorted Items • If an array is sorted (a[0] ≤ a[1] ≤ a[2] ≤...≤ a[a.length–1]) we can do better • Because we have an idea what kind of items are before and after an item we are examining • Example: assume a[5] == 7, then 7 <= a[5] <= a[i] for i>= 5 • Algorithm to search a sorted array for an item • m = a random index between 0 and (a.length - 1) • if (target == a[m]) • return m; • else if (target < a[m]) • return the result of searching a[0] through a[m - 1] • else if (target > a[m]) • return the result of searching a[m + 1] through a[a.length - 1] The if-else statement breaks the array in two parts Not the fastest way
Binary Search • Binary search involves picking the item m in the following algorithm to be the midpoint between 0 and (length - 1) • Example: assume a.length == 7, m = 3 or m = 4 • Algorithm to search a sorted array for an item • m = approximate midpoint between 0 and (a.length - 1) • if (target == a[m]) • return m; • else if (target < a[m]) • return the result of searching a[0] through a[m - 1] • else if (target > a[m]) • return the result of searching a[m + 1] through a[a.length - 1] Can we use this algorithm with recursion as is? The if-else statement breaks the array in two (almost) equally sized parts
Binary Search Using Recursion • To make our algorithm appropriate for recursion we need to generalize it a bit • It already includes a task that is a smaller version of the whole task • Algorithm to search a[first] through [last] of a sorted array for an item • m = approximate midpoint between first and last • if (target == a[m]) • return m; • else if (target < a[m]) • return the result of searching a[first] through a[m - 1] • else if (target > a[m]) • return the result of searching a[m + 1] through a[last] Another problem remains. Can you spot it?
Final Binary Search Using Recursion • To make our algorithm appropriate for recursion we need to generalize it a bit • It already includes a task that is a smaller version of the whole task • Algorithm to search a[first] through [last] of a sorted array for an item • m = approximate midpoint between first and last • if (first > last) • return -1; • else if (target == a[m]) • return m; • else if (target < a[m]) • return the result of searching a[first] through a[m - 1] • else if (target > a[m]) • return the result of searching a[m + 1] through a[last]
Binary Search Algorithm Visualization • http://www.dave-reed.com/book/Chapter8/search.html
Iterative Binary Search • m = approximate midpoint between first and last • if (target == a[m]) • return m; • else if (target < a[m]) • last = m – 1; • else if (target > a[m]) • first = m + 1; • Repeat while first < last • return -1; Recursive algorithm • m = approximate midpoint between first and last • if (first > last) • return -1; • else if (target == a[m]) • return m; • else if (target < a[m]) • return the result of searching a[first] through a[m - 1] • else if (target > a[m]) • return the result of searching a[m + 1] through a[last]
Iterative Binary Search int first = 0, last = a.length -1, m; while (first < last) { // calculate the midpoint for roughly equal partition m = midpoint(first, last); // determine which subarray to search if (target > a[m]) // change last index to search lower subarray last = m - 1; else if (target < a[m]) // change lower index to search upper subarray first= m + 1; else // we do not need to check m == target // key found at index m return m; } // key not found return -1; } • m = approximate midpoint between first and last • if (target == a[m]) • return m; • else if (target < a[m]) • last = m – 1; • else if (target > a[m]) • first = m + 1; • Repeat while first < last • return -1;
int first = 0, last = a.length -1, m; while (first < last) { // calculate the midpoint for roughly equal partition m = midpoint(first, last); // determine which subarray to search if (target > a[m]) // change last index to search lower subarray last = m - 1; else // change lower index to search upper subarray first = m + 1; } // postcondition at exit of loop // if target in a, then first == last, else first > last // Deferred equality test if (first == last && a[first] == target) return first; else // key not found return -1; } Optimizations • Performing branches within loops can be costly
Complexity of Binary Search • For an array with n elements, if we find the target in the first iteration, we check 1 element • Otherwise, if the length of the array is • odd, the array is split to segments of (n-1)/2 length • even, the array is split into a (n/2)-1 and a (n/2) segment • Worst case is x iterations • If the length of the array is even 2x = n • If the length of the array is odd 2x < n • 2x <= n log2(2x) <= log2(n) x <= log2(n) • Complexity is O(log2(n))
(Advanced) Issues with Binary Search • Reduced cache performance due to bad locality • Leeds to serial search being more effective on small datasets • The array must be sorted, so you need to take into account the time sort an array that it is not • Separate “keys” from actual data being searched • Take care of your bounds • Are they inclusive or exclusive • Be consistent • Can your array hold multiple items with the same value? • If yes, do you need to return all of them, the first, any, …? • Java offers a set of overloaded binarySearch() static methods in the class Arrays in the standard java.util package for performing binary searches • Must be arrays of primitives, or objects that implement the Comparable interface