CSC 222: Object-Oriented Programming

Slides:



Advertisements
Similar presentations
Recursion Chapter 14. Overview Base case and general case of recursion. A recursion is a method that calls itself. That simplifies the problem. The simpler.
Advertisements

1 CSC 427: Data Structures and Algorithm Analysis Fall 2008 Inheritance and efficiency  ArrayList  SortedArrayList  tradeoffs with adding/searching.
CSC 421: Algorithm Design & Analysis
Recursion. Recursion is a powerful technique for thinking about a process It can be used to simulate a loop, or for many other kinds of applications In.
1 CSC 427: Data Structures and Algorithm Analysis Fall 2006 Inheritance and efficiency  ArrayList  SortedArrayList  tradeoffs with adding/searching.
Chapter 10 Recursion Instructor: alkar/demirer. Copyright ©2004 Pearson Addison-Wesley. All rights reserved.10-2 Recursive Function recursive functionThe.
1 CSC 222: Computer Programming II Spring 2005 Searching and efficiency  sequential search  big-Oh, rate-of-growth  binary search  example: spell checker.
Department of Computer Science and Engineering, HKUST 1 HKUST Summer Programming Course 2008 Recursion.
Recursion, Complexity, and Searching and Sorting By Andrew Zeng.
Chapter 13 Recursion. Topics Simple Recursion Recursion with a Return Value Recursion with Two Base Cases Binary Search Revisited Animation Using Recursion.
Reynolds 2006 Complexity1 Complexity Analysis Algorithm: –A sequence of computations that operates on some set of inputs and produces a result in a finite.
1 CSC 222: Object-Oriented Programming Spring 2012 Searching and sorting  sequential search  algorithm analysis: big-Oh, rate-of-growth  binary search.
Recursion l Powerful Tool l Useful in simplifying a problem (hides details of a problem) l The ability of a function to call itself l A recursive call.
1 CSC 222: Object-Oriented Programming Spring 2012 recursion & sorting  recursive algorithms  base case, recursive case  silly examples: fibonacci,
1 CSC 222: Object-Oriented Programming Spring 2012 Object-oriented design  example: word frequencies w/ parallel lists  exception handling  System.out.format.
1 CSC 221: Computer Programming I Spring 2008 ArrayLists and arrays  example: letter frequencies  autoboxing/unboxing  ArrayLists vs. arrays  example:
Recursion, Complexity, and Sorting By Andrew Zeng.
1 Programming with Recursion. 2 Recursive Function Call A recursive call is a function call in which the called function is the same as the one making.
1 TCSS 143, Autumn 2004 Lecture Notes Recursion Koffman/Wolfgang Ch. 7, pp ,
1 CSC 427: Data Structures and Algorithm Analysis Fall 2008 Algorithm analysis, searching and sorting  best vs. average vs. worst case analysis  big-Oh.
CSC 221: Recursion. Recursion: Definition Function that solves a problem by relying on itself to compute the correct solution for a smaller version of.
1 CSC 421: Algorithm Design & Analysis Spring 2013 Divide & conquer  divide-and-conquer approach  familiar examples: merge sort, quick sort  other examples:
1 CSC 222: Object-Oriented Programming Spring 2013 Searching and sorting  sequential search vs. binary search  algorithm analysis: big-Oh, rate-of-growth.
1 CSC 222: Computer Programming II Spring 2004 Sorting and recursion  insertion sort, O(N 2 )  selection sort  recursion  merge sort, O(N log N)
Searching and Sorting Searching: Sequential, Binary Sorting: Selection, Insertion, Shell.
8.1 8 Algorithms Foundations of Computer Science  Cengage Learning.
Maitrayee Mukerji. Factorial For any positive integer n, its factorial is n! is: n! = 1 * 2 * 3 * 4* ….* (n-1) * n 0! = 1 1 ! = 1 2! = 1 * 2 = 2 5! =
CS 116 Object Oriented Programming II Lecture 13 Acknowledgement: Contains materials provided by George Koutsogiannakis and Matt Bauer.
Chapter 9 Recursion. Copyright ©2004 Pearson Addison-Wesley. All rights reserved.10-2 Recursive Function recursive functionThe recursive function is –a.
Recursion Powerful Tool
CSC 427: Data Structures and Algorithm Analysis
Algorithms.
Chapter 13 Recursion Copyright © 2016 Pearson, Inc. All rights reserved.
Sorting Mr. Jacobs.
CSC 222: Object-Oriented Programming Fall 2017
Recursion The programs discussed so far have been structured as functions that invoke one another in a disciplined manner For some problems it is useful.
CSC 222: Object-Oriented Programming
Chapter 15 Recursion.
Chapter 10 Recursion Instructor: Yuksel / Demirer.
CSC 427: Data Structures and Algorithm Analysis
OBJECT ORIENTED PROGRAMMING II LECTURE 23 GEORGE KOUTSOGIANNAKIS
CSC 222: Object-Oriented Programming
CSC 427: Data Structures and Algorithm Analysis
CSC 421: Algorithm Design & Analysis
CSC 421: Algorithm Design & Analysis
CSC 222: Object-Oriented Programming
Chapter 15 Recursion.
CSC 222: Object-Oriented Programming Fall 2017
CSC 421: Algorithm Design & Analysis
CSC 421: Algorithm Design & Analysis
CSC 421: Algorithm Design & Analysis
CSC 421: Algorithm Design & Analysis
Algorithm Analysis CSE 2011 Winter September 2018.
Recursion CSE 2320 – Algorithms and Data Structures
Recursion "To understand recursion, one must first understand recursion." -Stephen Hawking.
Algorithm design and Analysis
Recursion UW CSE 160 Winter 2017
Recursion Spring 2015 UW CSE 160
Recursion UW CSE 160 Spring 2018
Recursion Winter 2014 UW CSE 140
CSC 221: Computer Programming I Fall 2009
Basics of Recursion Programming with Recursion
Recursion UW CSE 160 Winter 2016
Analysis of Algorithms
CSC 421: Algorithm Design & Analysis
CSC 427: Data Structures and Algorithm Analysis
CSC 222: Object-Oriented Programming Spring 2013
CSC 222: Object-Oriented Programming Spring 2012
CSC 421: Algorithm Design & Analysis
7.2 Recursive Definitions of Mathematical Formulas
Presentation transcript:

CSC 222: Object-Oriented Programming Fall 2017 Sorting and recursion O(N log N) sorts: merge sort, quick sort recursion base case, recursive case recursion vs. iteration examples: letter counts, bit sequences

O(N log N) sorts there are sorting algorithms that do better than insertion & selection sorts merge sort & quick sort are commonly used O(N log N) sorts recall from sequential vs. binary search examples: when N is large, log N is much smaller than N thus, when N is large, N log N is much smaller than N2 N N log N N2 1,000 10,000 1,000,000 2,000 22,000 4,000,000 4,000 48,000 16,000,000 8,000 104,000 64,000,000 16,000 224,000 256,000,000 32,000 480,000 1,024,000,000 they are both recursive algorithms i.e., each breaks the list into pieces, calls itself to sort the smaller pieces, and combines the results

Recursion a recursive algorithm is one that refers to itself when solving a problem to solve a problem, break into smaller instances of problem, solve & combine recursion can be a powerful design & problem-solving technique examples: binary search, merge sort, hierarchical data structures, … classic (but silly) examples: Fibonacci numbers: 1st Fibonacci number = 1 2nd Fibonacci number = 1 Nth Fibonacci number = (N-1)th Fibonacci number + (N-2)th Fibonacci number Euclid's algorithm to find the Greatest Common Divisor (GCD) of a and b (a ≥ b) if a % b == 0, the GCD(a, b) = b otherwise, GCD(a, b) = GCD(b, a % b)

Recursive methods these are classic examples, but pretty STUPID /** * Computes Nth Fibonacci number. * @param N sequence index * @returns Nth Fibonacci number */ public int fibonacci(int N) { if (N <= 2) { return 1; } else { return fibonacci(N-1) + fibonacci(N-2); /** * Computes Greatest Common Denominator. * @param a a positive integer * @param b positive integer (a >= b) * @returns GCD of a and b */ public int GCD(int a, int b) { if (a % b == 0) { return b; } else { return GCD(b, a%b); these are classic examples, but pretty STUPID both can be easily implemented using iteration (i.e., loops) recursive approach to Fibonacci has huge redundancy we will look at better examples later, but first analyze these simple ones

Understanding recursion every recursive definition has 2 parts: BASE CASE(S): case(s) so simple that they can be solved directly RECURSIVE CASE(S): more complex – make use of recursion to solve smaller subproblems & combine into a solution to the larger problem int fibonacci(int N) { if (N <= 2) { // BASE CASE return 1; } else { // RECURSIVE CASE return fibonacci(N-1) + fibonacci(N-2); int GCD(int a, int b) { if (a % b == 0) { // BASE CASE return b; } else { // RECURSIVE CASE return GCD(b, a%b); to verify that a recursive definition works: convince yourself that the base case(s) are handled correctly ASSUME RECURSIVE CALLS WORK ON SMALLER PROBLEMS, then convince yourself that the results from the recursive calls are combined to solve the whole

Avoiding infinite(?) recursion to avoid infinite recursion: must have at least 1 base case (to terminate the recursive sequence) each recursive call must get closer to a base case int fibonacci(int N) { if (N <= 2) { // BASE CASE return 1; } else { // RECURSIVE CASE return fibonacci(N-1) + fibonacci(N-2); int GCD(int a, int b) { if (a % b == 0) { // BASE CASE return b; } else { // RECURSIVE CASE return GCD(b, a%b); with each recursive call, the number is getting smaller  closer to base case (≤ 2) with each recursive call, a & b are getting smaller  closer to base case (a % b == 0)

Merge sort a better example of recursion is merge sort 12 9 6 20 3 15 BASE CASE: to sort a list of 0 or 1 item, DO NOTHING! RECURSIVE CASE: Divide the list in half Recursively sort each half using merge sort Merge the two sorted halves together 12 9 6 20 3 15 1. 12 9 6 20 3 15 2. 6 9 12 3 15 20 3. 3 6 9 12 15 20

Merging two sorted lists merging two lists can be done in a single pass since sorted, need only compare values at front of each, select smallest requires additional list structure to store merged items public <T extends Comparable<? super T>> void merge(ArrayList<T> items, int low, int high) { ArrayList<T> copy = new ArrayList<T>(); int size = high-low+1; int middle = (low+high+1)/2; int front1 = low; int front2 = middle; for (int i = 0; i < size; i++) { if (front2 > high || (front1 < middle && items.get(front1).compareTo(items.get(front2)) < 0)) { copy.add(items.get(front1)); front1++; } else { copy.add(items.get(front2)); front2++; for (int k = 0; k < size; k++) { items.set(low+k, copy.get(k));

Merge sort once merge has been written, merge sort is simple for recursion to work, need to be able to specify range to be sorted initially, want to sort the entire range of the list (index 0 to list size – 1) recursive call sorts left half (start to middle) & right half (middle to end) . . . public <T extends Comparable<? super T>> void mergeSort(ArrayList<T> items) { mergeSort(items, 0, items.size()-1); } private <T extends Comparable<? super T>> void mergeSort(ArrayList<T> items, int low, int high) { if (low < high) { int middle = (low + high)/2; mergeSort(items, low, middle); mergeSort(items, middle+1, high); merge(items, low, high); note: private helper method does the recursion; public method calls the helper with appropriate inputs Collections class contains a sort method that implements quick sort can be called on any List of objects, as long as they implement they are Comparable

Recursive analysis of a recursive algorithm cost of sorting N items = cost of sorting left half (N/2 items) + cost of sorting right half (N/2 items) + cost of merging (N items) more succinctly: Cost(N) = 2*Cost(N/2) + C1*N Cost(N) = 2*Cost(N/2) + C1*N can unwind Cost(N/2) = 2*( 2*Cost(N/4) + C2*N/2 ) + C1*N = 4*Cost(N/4) + (C1 + C2)*N can unwind Cost(N/4) = 4*( 2*Cost(N/8) + C3*N/4 ) + (C1 + C2)*N = 8*Cost(N/8) + (C1 + C2 + C3)*N can continue unwinding = … = N*Cost(1) + (C1 + C2/2 + C3/4 + … + ClogN/N)*N = (C0 + C1 + C2 + C3 + … + ClogN )*N where C0 = Cost(1) ≤ (max(C0, C1,…, ClogN)*log N) * N = C * N log N where C = max(C0, C1,…, ClogN)  O(N log N)

Quick sort quick sort is another O(N log N) sorting algorithm 1. in practice, quick sort is a faster O(N log N) sort than merge sort picks a pivot element from the list (can do this at random or be smarter) partitions the list so that all items ≤ pivot are to left, all items > pivot are to right recursively (quick) sorts the partitions 1. 12 9 6 20 3 15 2. 9 6 3 12 20 15 3. 3 6 9 12 15 20

Dictionary revisited recall most recent version of Dictionary inserts each new word in order (i.e., insertion sort) & utilizes binary search searching is fast (binary search), but adding is slow N adds + N searches: N*O(N) + N*O(log N) = O(N2) + O(N log N) = O(N2) if you are going to do lots of adds in between searches: simply add each item at the end  O(1) before the first search, must sort – could use merge sort N adds + sort + N searches: N*O(1) + O(N log N) + N*log(N) = O(N log N) since Strings are Comparable with one another we can use Collections.sort to sort the Dictionary words

Modified Dictionary class public class Dictionary2 { private ArrayList<String> words; private boolean isSorted; public Dictionary2() { this.words = new ArrayList<String>(); this.isSorted = true; } public Dictionary2(String fileName) { this(); try { Scanner infile = new Scanner(new File(fileName)); while (infile.hasNext()) { String nextWord = infile.next(); this.addWord(nextWord); infile.close(); catch (java.io.FileNotFoundException e) { System.out.println("No such file: " + fileName); public boolean addWord(String newWord) { this.words.add(newWord.toLowerCase()); this.isSorted = false; return true; public boolean findWord(String desiredWord) { if (!this.isSorted) { Collections.sort(this.words); return Collections.binarySearch(this.words, desiredWord.toLowerCase()) >= 0; . . . the isSorted field keeps track of whether the list is sorted (i.e., no addWords have been performed since last findWord) we could do a little more work in addWord to avoid unnecessary sorts note: gives better performance if N adds are followed by N searches what if the adds & searches alternate?

Dictionary2 timings N adds followed by N searches: Dictionary1 used insertion sort & binary search O(N2) + O(N log N)  O(N2) Dictionary2 uses add-at-end, mergesort before first search, then binary search O(N) + O(N log N) + O(N log N)  O(N log N) import java.util.Random; public class TimeDictionary { public static int timeAdds(int numValues) { Dictionary2 dict = new Dictionary2(); Random randomizer = new Random(); long startTime = System.currentTimeMillis(); for (int i = 0; i < numValues; i++) { String word = "0000000000" + randomizer.nextInt(); dict.addWord(word.substring(word.length()-10)); } dict.findWord("zzz"); long endTime = System.currentTimeMillis(); return (int)(endTime-startTime); # items (N) Dictionary1 (msec) Dictionary2 (msec) 100,000 2121 176 200,000 8021 386 400,000 31216 864

Recursion vs. iteration it wouldn't be difficult to code fibonacci and GCD without recursion public int fibonacci(int N) { public int GCD(int a, int b) { int previous = 1; while (a % b != 0) { int current = 1; int temp = b; for (int i = 3; i <= N; i++) { b = a % b; int newCurrent = current + previous; a = temp; previous = current; } current = newCurrent; return b; } } return current; } in theory, any recursive algorithm can be rewritten iteratively (using a loop) but sometimes, a recursive definition is MUCH clearer & MUCH easier to write e.g., merge sort & quick sort

Recursion & efficiency there is some overhead cost associated with recursion public int fibonacci(int N) { public int GCD(int a, int b) { int previous = 1; while (a % b != 0) { int current = 1; int temp = b; for (int i = 3; i <= N; i++) { b = a % b; int newCurrent = current + previous; a = temp; previous = current; } current = newCurrent; return b; } } return current; } with recursive version: each refinement requires a method call involves saving current execution state, allocating memory for the method instance, allocating and initializing parameters, returning value, … with iterative version: each refinement involves a loop iteration + assignments the cost of recursion is relatively small, so usually no noticeable difference in practical terms, there is a limit to how deep recursion can go e.g., can't calculate the 10 millionth fibonacci number in the rare case that recursive depth can be large (> 1,000), consider iteration

Recursion & redundancy in the case of GCD, there is only a minor efficiency difference number of recursive calls = number of loop iterations this is not always the case  efficiency can be significantly different (due to different underlying algorithms) consider the recursive fibonacci method: fibonacci(5) fibonacci(4) + fibonacci(3) fibonacci(3) + fibonacci(2) fibonacci(2) + fibonacci(1) fibonacci(2) + fibonacci(1) there is a SIGNIFICANT amount of redundancy in the recursive version number of recursive calls > number of loop iterations (by an exponential amount!) recursive version is MUCH slower than the iterative one in fact, it bogs down on relatively small values of N

More examples many problems that can be solved with a loop can also be solved using recursion e.g., count the number of e's in a word set a counter to 0 loop through each character in the word if the character is 'e', increment the counter recursive approach: BASE CASE: empty string  0 RECURSIVE CASE: calculate the # of e's in the substring starting at index 1, then add 1 if the first character in the string is an e eCountRec("eerie")  eCountRec("erie") + 1 eCountRec("Letter")  eCountRec("etter")

eCount implementations public int eCountLoop(String str) { int count = 0; for (int i = 0; i < str.length(); i++) { if (str.charAt(i) == 'e') { count++; } return count; public int eCountRec(String str) { if (str.length() == 0) { return 0; else { int count = this.eCountRec(str.substring(1, str.length())); if (str.charAt(0) == 'e') {

Similar example suppose we want to convert a binary number into decimal 110012 = 1*1610 + 1*8 + 0*410 + 0*210 + 1*110 = 2510 looping solution set a sum to 0 loop through the bits, going from right to left if bit is 1, add corresponding power of 2 to the sum recursive solution BASE CASE: empty string  0 RECURSIVE CASE: convert the substring up to the last character, multiply by 2, then add 1 if the last bit is a 1 valueOfRec("11001")  2*valueOfRec("1100") + 1

Similar example public int binValueLoop(String bitString) { int total = 0; int posValue = 1; for (int i = bitString.length()-1; i >= 0; i--) { if (bitString.charAt(i) == '1') { total += posValue; } posValue *= 2; return total; public int binValueRec(String bitString) { if (bitString.length() == 0) { return 0; else { String prefix = bitString.substring(0, bitString.length()-1); int value = 2*this.binValueRec(prefix); if (bitString.charAt(bitString.length()-1) == '1') { value++; return value;

Better example consider a different task: generating all of the bit sequences of a given length showBits(2)  00 showBits(3)  000 01 001 10 010 11 011 100 101 110 111 this is a difficult task to complete using a loop, but fairly easy with recursion but, need an extra parameter, representing a prefix for the sequences start with showBits(3, "") BASE CASE: if the prefix is the desired length, print it RECURSIVE CASE: otherwise, print all sequences with prefix+"0", followed by all sequences with prefix+"1"

showBits implementation public void showBits(int len) { this.showBits(len, ""); } private void showBits(int len, String prefix) { if (prefix.length() == len) { System.out.println(prefix); else { this.showBits(len, prefix+"0"); this.showBits(len, prefix+"1"); we will see more examples of recursion in CSC321