Recursion Based on slides by Alyssa Harding CSE 143 Lecture 13 Recursion Based on slides by Alyssa Harding
Recursion recursion: The definition of an operation in terms of itself. Solving a problem using recursion depends on solving smaller occurrences of the same problem. recursive programming: Writing methods that call themselves to solve problems recursively. An equally powerful substitute for iteration (loops) Particularly well-suited to solving certain types of problems
Why learn recursion? Many programming languages ("functional" languages such as Scheme, ML, and Haskell) use recursion exclusively (no loops) "cultural experience" - A different way of thinking of problems Can solve some kinds of problems better than iteration Leads to elegant, simplistic, short code (when used well) A key component of the rest of our assignments in CSE 143
Decimal Numbers vs. Binary Numbers Decimal Numbers (“Base 10”) 10 Possible Digits: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 Ex. With 3 digits we get: 10 * 10 * 10 different patterns (0-999) We interpret these patterns as follows: 34810 = 300 + 40 + 8 = 3 ×102 + 4 ×101 + 8×100 Binary Numbers (“Base 2”) 2 Possible Digits: 0, 1 Ex. With 3 digits we get: 2 * 2 * 2 different patterns (000-111) 1012 = 1 ×22 + 0 ×21 + 1×20 = 1 ×4 + 0 ×2 + 1×1 = 4 + 1 = 510 4
Binary Numbers A few Decimal numbers written in Binary: Decimal Binary Sum 0 × 20 1 1 × 20 2 10 1 × 21 + 0 × 20 3 11 1 × 21 + 1 × 20 4 100 1 × 22 + 0 × 21 + 0 × 20 5 101 1 × 22 + 0 × 21 + 1 × 20 6 110 1 × 22 + 1 × 21 + 0 × 20 7 111 1 × 22 + 1 × 21 + 1 × 20 8 1000 1 × 23 + 0 × 22 + 0 × 21 + 0 × 20
Example: writeBinary Write a method that takes a positive decimal number as input and writes out the equivalent number in binary. How do we convert from decimal to binary? Example: What is 8574 in binary? How about the last binary digit of 8574? It’ll be 0, just like all the other even numbers in the table
See Stutter from Monday Example: writeBinary This gives us an idea of how to break our problem down. Recall that we can split a decimal number to get the leading digits in base-10 and the final digit, given number 8574: To get the leading digits in base-2 and the final digit, we can do the same, given decimal number 8574: 857 4 n/10 n%10 See Stutter from Monday “The number of 10s in 8574” 4287 n/2 n%2 “The number of 2s in 8574”
Example: writeBinary So, what’s the base case? public static void writeBinary(int n) { }
Example: writeBinary So, what’s the base case? public static void writeBinary(int n) { if ( n < 2 ) { System.out.print(n); } else { ... } } We know what to do with a single binary digit, 0 or 1 9
Example: writeBinary What’s the recursive case? public static void writeBinary(int n) { if ( n < 2 ) { System.out.print(n); } else { ... System.out.print(n%2); } } If we have a bigger number, we can still print out its last binary digit
Example: writeBinary What’s the recursive case? public static void writeBinary(int n) { if ( n < 2 ) { System.out.print(n); } else { writeBinary(n/2); System.out.print(n%2); } } And we can recurse to print out the remaining part of the number
Another Example: sum public static int sum(int[] list) { int sum = 0; Write a method sum that takes an array of integers as a parameter and returns the sum of the numbers in the array. public static int sum(int[] list) { int sum = 0; for (int i = 0; i < list.length; i++) { sum += list[i]; } return sum; Iterative version
Example: sum Write a method sum that takes an array of integers as a parameter and returns the sum of the numbers in the array using recursion. public static int sum(int[] list) { } Base Case?
Example: sum Write a method sum that takes an array of integers as a parameter and returns the sum of the numbers in the array using recursion. public static int sum(int[] list) { if (list.length == 0) return 0; else { ... } Base Case Break problem into smaller pieces Make progress towards base case with each recursive call
Example: sum public static int sum(int[] list) { if (list.length == 0) return 0; else { ... } Break problem into smaller pieces, make progress: sum(entire list) = list[0] + sum(list starting at 1)
Example: sum When do we stop? Break problem into smaller pieces, make progress: sum(entire list) = list[0] + sum(list starting at 1) sum(list starting at 1) = list[1] + sum(list starting at 2) sum(list starting at 2) = list[2] + sum(list starting at 3) sum(list starting at 3) = list[3] + sum(list starting at 4) sum(list starting at 4) = list[4] + sum(list starting at 5) ..... When do we stop?
Example: sum Break problem into smaller pieces, make progress: sum(entire list) = list[0] + sum(list starting at 1) sum(list starting at 1) = list[1] + sum(list starting at 2) sum(list starting at 2) = list[2] + sum(list starting at 3) sum(list starting at 3) = list[3] + sum(list starting at 4) sum(list starting at 4) = list[4] + sum(list starting at 5) ..... sum(list starting at list.length-1) = list[list.length-1] + sum(list starting at list.length) sum(list starting at list.length) = 0
Example: sum Current version: public static int sum(int[] list) { if (list.length == 0) return 0; else { ... }
Example: sum Orig version: public static int sum(int[] list) { if (list.length == 0) return 0; else { ... } New version: public static int sum(int[] list, int index) {
Example: sum public static int sum(int[] list, int index) { if (index == list.length) return 0; else return list[index] + sum(list, index + 1); }
Example: sum // returns the sum of the numbers in the given array public int sum(int[] list) { return sum(list, 0); } // computes the sum of the list starting at the given index private static int sum(int[] list, int index) { if (index == list.length) return 0; else return list[index] + sum(list, index + 1);
Aside: for each loop There’s another way to look at any structure that is Iterable for (int i = 0; i < <structure>.size(); i++) { <statements> } for (<type> <name> : <structure>) { <statements> } Warning: in a for each loop, you cannot remove values see which index you’re at If you need to do that, stick with a for loop! for loop for each loop 22
for loop vs. for each loop ArrayList<Integer> list1 = new ArrayList<Integer>(); list1.add(42); list1.add(7); list1.add(-10); for (int i = 0; i < list1.size(); i++) { System.out.println(list1.get(i)); } for (int n : list1) { System.out.println(n); } for loop for each loop 23
Recursion Example: crawler Now we want to make a crawler method that will print out files and directories Java provides us with a File object http://java.sun.com/javase/6/docs/api/java/io/File.html Given the file or directory we want, we can print its name public static void print(File f) { System.out.println(f.getName()); } We also want to print the contents of a directory, though
Example: crawler We can get an array of the files in the directory and print their names: public static void print(File f) { System.out.println(f.getName()); for (File subF : f.listFiles()) { System.out.println(“ “+subF.getName()); } }
Example: crawler We can get an array of the files in the directory and print their names: public static void print(File f) { System.out.println(f.getName()); for (File subF : f.listFiles()) { System.out.println(“ “+subF.getName()); } } But this only works if f is a directory, otherwise it throws a NullPointerException 26
Example: crawler So we check to make sure f is a directory first: public static void print(File f) { System.out.println(f.getName()); if ( f.isDirectory() ) { for (File subF : f.listFiles()) { System.out.println(" “+subF.getName()); } } }
Example: crawler So we check to make sure f is a directory first: public static void print(File f) { System.out.println(f.getName()); if ( f.isDirectory() ) { for (File subF : f.listFiles()) { System.out.println(" “+subF.getName()); } } } But this still only works for one level of directories 28
Example: crawler So we check to make sure f is a directory first: public static void print(File f) { System.out.println(f.getName()); if ( f.isDirectory() ) { for (File subF : f.listFiles()) { System.out.println(" “+subF.getName()); if ( subF.isDirectory() ) { for (File subSubF : subF.listFiles() ) { ... } } } } } 29
Example: crawler So we check to make sure f is a directory first: public static void print(File f) { System.out.println(f.getName()); if ( f.isDirectory() ) { for (File subF : f.listFiles()) { System.out.println(" “+subF.getName()); if ( subF.isDirectory() ) { for (File subSubF : subF.listFiles() ) { ... } } } } } Redundant! And it still only works for one more level of directories
Example: crawler If only we had a method that would print the files in a directory...but we do! public static void print(File f) { System.out.println(f.getName()); if ( f.isDirectory() ) { for (File subF : f.listFiles()) { print(subF); } } } We probably want to add indentation, as well
Example: crawler So we add a String parameter to show how much each file name should be indented public static void print(File f, String indent) { System.out.println(indent + f.getName()); if ( f.isDirectory() ) { for (File subF : f.listFiles()) { print(subF, indent + " "); } } } We don’t want our client to need to pass a String, though
Example: crawler Instead, we make a public method for the client and hide the extra parameter by making our other method private: public static void print(File f) { print(f, ""); } private static void print(File f, String indent) { System.out.println(indent + f.getName()); if ( f.isDirectory() ) { for (File subF : f.listFiles()) { print(subF, indent + " "); } } } Using a public/private pair is common in recursion when we want to work with extra parameters
Example: Sierpinski Chapter 12 in the book discusses the Sierpinski triangle, a fractal (recursive image):
Extra Example: pow Now we want to recursively take x to the y power We’ll only deal with ints, so x and y must be ints This also means that y must be positive so that the return value is an int 35
Example: pow What’s the base case? // returns x to the y power // pre: y >= 0 public static int pow(int x, int y) { if ( y == 0 ) { return 1; } else { ... } } If y is 0, we automatically know that the power is 1! 36
Example: pow What’s the recursive case? // returns x to the y power // pre: y >= 0 public static int pow(int x, int y) { if ( y == 0 ) { return 1; } else { return x * pow(x,y-1); } } Otherwise, we can break it down into two smaller problems: xy = x1 × xy-1 37
Example: pow Taking care of negative inputs... // returns x to the y power // if y < 0, throws IllegalArgumentException public static int pow(int x, int y) { if ( y < 0 ) { throw new IllegalArgumentException(); } else if ( y == 0 ) { return 1; } else { return x * pow(x,y-1); } } 38
Example: pow But is this the best way? If I asked you to compute 232, you wouldn’t hit multiply on your calculator 32 times (hopefully!) We can also note that xy = (x2)(y/2) Example: 34 = 92 = 81 This only works for even values of y because of integer division 39
Example: pow Putting in our new idea... public static int pow2(int x, int y) { if ( y < 0 ) { throw new IllegalArgumentException(); } else if ( y == 0 ) { return 1; } else if ( y % 2 == 0 ) { return pow2(x*x, y/2); } else { return x * pow2(x,y-1); } } 40