58
Recursion: Thinking About It 8 October 2013 OSU CSE 1

Recursion: Thinking About It - Computer Science and ...web.cse.ohio-state.edu/.../extras/slides/21.Recursion-Thinking.pdf · Recursion • A remarkably important concept and programming

Embed Size (px)

Citation preview

Recursion: Thinking About It

8 October 2013 OSU CSE 1

Recursion

• A remarkably important concept and programming technique in computer science is recursion– A recursive method is simply one that calls

itself • There are two quite different views of

recursion!– We ask for your patience as we introduce

them one at a time...

8 October 2013 OSU CSE 2

Question Considered Now

• How should you think about recursion so you can use it to develop elegant recursive methods to solve certain problems?

8 October 2013 OSU CSE 3

Question Considered Next

• Why do those recursive methods work?

8 October 2013 OSU CSE 4

Question Considered Only Later

• How do those recursive methods work?– Don’t worry; we will come back to this– If you start by insisting on knowing the answer

to this question, you may never be fully capable of developing elegant recursive solutions to problems!

8 October 2013 OSU CSE 5

Suppose...

• You need to reverse a String• Contract specification looks like this:

/*** Reverses a String.* ...* @ensures* reversedString = rev(s)

*/private static String reversedString(String s) {...}

8 October 2013 OSU CSE 6

Suppose...

• You need to reverse a String• Contract specification looks like this:

/*** Reverses a String.* ...* @ensures* reversedString = rev(s)

*/private static String reversedString(String s) {...}

8 October 2013 OSU CSE 7

Try to implement it

(i.e., write the method body).

One Possible Solutionprivate static String reversedString(String s) {

String rs = "";for (int i = 0; i < s.length(); i++) {rs = s.charAt(i) + rs;

}return rs;

}

8 October 2013 OSU CSE 8

Trace It

8 October 2013 OSU CSE 9

s = "abc"rs = ""

for (int i = 0; i < s.length(); i++) {

rs = s.charAt(i) + rs;

}

Trace It: Iteration 1

8 October 2013 OSU CSE 10

s = "abc"rs = ""

for (int i = 0; i < s.length(); i++) {

s = "abc"rs = ""i = 0

rs = s.charAt(i) + rs;

}

Trace It: Iteration 1

8 October 2013 OSU CSE 11

s = "abc"rs = ""

for (int i = 0; i < s.length(); i++) {

s = "abc"rs = ""i = 0

rs = s.charAt(i) + rs;

s = "abc"rs = "a"i = 0

}

Trace It: Iteration 2

8 October 2013 OSU CSE 12

s = "abc"rs = ""

for (int i = 0; i < s.length(); i++) {

s = "abc"rs = "a"i = 1

rs = s.charAt(i) + rs;

s = "abc"rs = "a"i = 0

}

Trace It: Iteration 2

8 October 2013 OSU CSE 13

s = "abc"rs = ""

for (int i = 0; i < s.length(); i++) {

s = "abc"rs = "a"i = 1

rs = s.charAt(i) + rs;

s = "abc"rs = "ba"i = 1

}

Trace It: Iteration 3

8 October 2013 OSU CSE 14

s = "abc"rs = ""

for (int i = 0; i < s.length(); i++) {

s = "abc"rs = "ba"i = 2

rs = s.charAt(i) + rs;

s = "abc"rs = "ba"i = 1

}

Trace It: Iteration 3

8 October 2013 OSU CSE 15

s = "abc"rs = ""

for (int i = 0; i < s.length(); i++) {

s = "abc"rs = "ba"i = 2

rs = s.charAt(i) + rs;

s = "abc"rs = "cba"i = 2

}

Trace It: Ready to Return

8 October 2013 OSU CSE 16

s = "abc"rs = ""

for (int i = 0; i < s.length(); i++) {

s = "abc"rs = "ba"i = 2

rs = s.charAt(i) + rs;

s = "abc"rs = "cba"i = 2

}

s = "abc"rs = "cba"

Oh, Did I Mention...

• There is already a static method in the class FreeLunch, with exactly the same contract:/*** Reverses a String.* ...* @ensures* reversedString = rev(s)

*/private static String reversedString(String s) {...}

8 October 2013 OSU CSE 17

A Free Lunch Sounds Good!

• The slightly nasty thing about the FreeLunch class is that its methods will not directly solve your problem: you have to make your problem “smaller” first

• This reversedString code will not work:private static String reversedString(String s) {

return FreeLunch.reversedString(s);}

8 October 2013 OSU CSE 18

Recognizing the Smaller Problem

• A key to recursive thinking is the ability to recognize some smaller instance of the same problem “hiding inside” the problem you need to solve

• Here, suppose we recognize the following property of string reversal:rev(<x> * a) = rev(a) * <x>

8 October 2013 OSU CSE 19

The Smaller Problem

• If we had some way to reverse a string of length 4, say, then we could reverse a string of length 5 by:– removing the character on the left end– reversing what’s left– adding the character that was removed onto

the right end

8 October 2013 OSU CSE 20

The Smaller Problem

• If we had some way to reverse a string of length 4, say, then we could reverse a string of length 5 by:– removing the character on the left end– reversing what’s left– adding the character that was removed onto

the right end

8 October 2013 OSU CSE 21

This is a smaller instance of exactly the same problem as we need to solve.

Time for Our Free Lunch

• We can use the FreeLunch class now:private static String reversedString(String s) {

String sub = s.substring(1);String revSub =FreeLunch.reversedString(sub);

String result = revSub + s.charAt(0);return result;

}

8 October 2013 OSU CSE 22

Trace It

8 October 2013 OSU CSE 23

s = "abc"

String sub = s.substring(1);

s = "abc"sub = "bc"

String revSub = FreeLunch.reversedString(sub);

s = "abc"sub = "bc"revSub = "cb"

String result = revSub + s.charAt(0);

s = "abc"sub = "bc"revSub = "cb"result = "cba"

Trace It

8 October 2013 OSU CSE 24

s = "abc"

String sub = s.substring(1);

s = "abc"sub = "bc"

String revSub = FreeLunch.reversedString(sub);

s = "abc"sub = "bc"revSub = "cb"

String result = revSub + s.charAt(0);

s = "abc"sub = "bc"revSub = "cb"result = "cba"

How do you trace over this call? By looking at the contract, as usual!

Almost Done With Lunch

• Is this code correct?private static String reversedString(String s) {

String sub = s.substring(1);String revSub =FreeLunch.reversedString(sub);

String result = revSub + s.charAt(0);return result;

}

8 October 2013 OSU CSE 25

Almost Done With Lunch

• Is this code correct?private static String reversedString(String s) {

String sub = s.substring(1);String revSub =FreeLunch.reversedString(sub);

String result = revSub + s.charAt(0);return result;

}

8 October 2013 OSU CSE 26

This call has a precondition: s must not be the empty string (which can be gleaned from

the String API with a careful reading).

Almost Done With Lunch

• Is this code correct?private static String reversedString(String s) {

String sub = s.substring(1);String revSub =FreeLunch.reversedString(sub);

String result = revSub + s.charAt(0);return result;

}

8 October 2013 OSU CSE 27

This call has a precondition: s must not be the empty string (which can be gleaned from

the String API with a careful reading).

Accounting for Empty sprivate static String reversedString(String s) {

if (s.length() == 0) {return s;

} else {String sub = s.substring(1);String revSub =

FreeLunch.reversedString(sub);String result = revSub + s.charAt(0);return result;

}}

8 October 2013 OSU CSE 28

Accounting for Empty sprivate static String reversedString(String s) {

if (s.length() == 0) {return s;

} else {String sub = s.substring(1);String revSub =

FreeLunch.reversedString(sub);String result = revSub + s.charAt(0);return result;

}}

8 October 2013 OSU CSE 29

This test could also be done as:s.equals("")

but not as:s == ""

Accounting for Empty sprivate static String reversedString(String s) {

if (s.length() == 0) {return s;

} else {String sub = s.substring(1);String revSub =

FreeLunch.reversedString(sub);String result = revSub + s.charAt(0);return result;

}}

8 October 2013 OSU CSE 30

Returning an empty string could also be written as:return "";

Oh, Did I Mention...

• Sorry, there is no FreeLunch!

8 October 2013 OSU CSE 31

There Is No FreeLunch?!?private static String reversedString(String s) {

if (s.length() == 0) {return s;

} else {String sub = s.substring(1);String revSub =

FreeLunch.reversedString(sub);String result = revSub + s.charAt(0);return result;

}}

8 October 2013 OSU CSE 32

There Is No FreeLunch?!?private static String reversedString(String s) {

if (s.length() == 0) {return s;

} else {String sub = s.substring(1);String revSub =

reversedString(sub);String result = revSub + s.charAt(0);return result;

}}

8 October 2013 OSU CSE 33

We Don’t Need a FreeLunchprivate static String reversedString(String s) {

if (s.length() == 0) {return s;

} else {String sub = s.substring(1);String revSub =

reversedString(sub);String result = revSub + s.charAt(0);return result;

}}

8 October 2013 OSU CSE 34

We just wrote the code for reversedString, so we can call our own version rather than the

one from FreeLunch.

A Recursive Methodprivate static String reversedString(String s) {

if (s.length() == 0) {return s;

} else {String sub = s.substring(1);String revSub =

reversedString(sub);String result = revSub + s.charAt(0);return result;

}}

8 October 2013 OSU CSE 35

Note that the body of reversedString now calls itself, so we just wrote a recursive method.

Crucial Theorem for Recursion

• If your code for a method is correct when it calls the (hypothetical) FreeLunchversion of the method — remember, it must be on a smaller instance of the problem — then your code is still correct when you replace every call to the FreeLunch version with a recursive call to your own version

8 October 2013 OSU CSE 36

Theorem Applied

• If the code that makes a call to FreeLunch.reversedString is correct, then so is the code that makes a recursive call to reversedString

• Remember: this is so only because the call to FreeLunch.reversedString is for a smaller problem, i.e., a string with smaller length

8 October 2013 OSU CSE 37

No Need For Multiple Returnsprivate static String reversedString(String s) {

String result = s;if (s.length() > 0) {String sub = s.substring(1);String revSub = reversedString(sub);result = revSub + s.charAt(0);

}return result;

}

8 October 2013 OSU CSE 38

Alternative solution with a single return. In this case, multiple returns are not necessary

and they do not provide a better solution.

Another Example: Suppose...

• You need to increment a NaturalNumber/*** Increments a NaturalNumber.* ...* @updates n* @ensures* n = #n + 1

*/private static void increment (NaturalNumber n) {...}

8 October 2013 OSU CSE 39

Another Example: Suppose...

• You need to increment a NaturalNumber/*** Increments a NaturalNumber.* ...* @updates n* @ensures* n = #n + 1

*/private static void increment (NaturalNumber n) {...}

8 October 2013 OSU CSE 40

Try to implement it (i.e., write the method body) using only

the kernel methods:multiplyBy10divideBy10isZero

Not So Easy

• Unlike string reversal, there is no straightforward iterative solution to this problem

• So, let’s try a recursive solution...• Can you recognize the smaller problem?

8 October 2013 OSU CSE 41

Recognizing the Smaller Problem

• Think about how you would increment (add 1 to) a number using the grade-school arithmetic algorithm

• Examples:

8 October 2013 OSU CSE 42

41072+ 141073

41079+ 141080

41999+ 142000

Recognizing the Smaller Problem

• Think about how you would increment (add 1 to) a number using the grade-school arithmetic algorithm

• Examples:

8 October 2013 OSU CSE 43

41072+ 141073

41079+ 141080

41999+ 142000

The Smaller Problem

• If we had some way to increment a number with 4 digits, say, then we could increment a 5-digit number by:– taking off the one’s digit– incrementing it and asking: is there is a “carry”?– if there is, then incrementing what’s left– putting back the updated one’s digit

• Important: multiple carries don’t matter

8 October 2013 OSU CSE 44

The Smaller Problem

• If we had some way to increment a number with 4 digits, say, then we could increment a 5-digit number by:– taking off the one’s digit– incrementing it and asking: is there is a “carry”?– if there is, then incrementing what’s left– putting back the updated one’s digit

• Important: multiple carries don’t matter

8 October 2013 OSU CSE 45

This is a smaller instance of exactly the same problem as we need to solve.

Time for Our Free Lunch

• We can use the FreeLunch class now:private static void increment (NaturalNumber n) {

int onesDigit = n.divideBy10();onesDigit++;if (onesDigit == 10) {onesDigit = 0;FreeLunch.increment(n);

}n.multiplyBy10(onesDigit);

}

8 October 2013 OSU CSE 46

Almost Done With Lunch

• Is this code correct?private static void increment (NaturalNumber n) {

int onesDigit = n.divideBy10();onesDigit++;if (onesDigit == 10) {onesDigit = 0;FreeLunch.increment(n);

}n.multiplyBy10(onesDigit);

}

8 October 2013 OSU CSE 47

Done With Lunch

• Is this code correct?private static void increment (NaturalNumber n) {

int onesDigit = n.divideBy10();onesDigit++;if (onesDigit == 10) {onesDigit = 0;increment(n);

}n.multiplyBy10(onesDigit);

}

8 October 2013 OSU CSE 48

Theorem Applied

• If the code that makes a call to FreeLunch.increment is correct, then so is the code that makes a recursive call to increment

• Remember: this is so only because the call to FreeLunch.increment is for a smaller problem, i.e., a number less than the incoming value of n

8 October 2013 OSU CSE 49

Another Example/*** Raises an int to a power.* ...* @requires* p >= 0 and [n ^ (p) is within int range]* @ensures* power = n ^ (p)*/

private static int power(int n, int p) {...}

8 October 2013 OSU CSE 50

A Hidden Smaller Problem

• Can you recognize a smaller problem of the same kind hiding inside the computation of np?

• Here is a mathematical property that might help you see one:np = n * np-1 (for p > 0)

8 October 2013 OSU CSE 51

A Hidden Smaller Problem

• Can you recognize a smaller problem of the same kind hiding inside the computation of np?

• Here is a mathematical property that might help you see one:np = n * np-1 (for p > 0)

8 October 2013 OSU CSE 52

A Hidden Smaller Problem

• Can you recognize a smaller problem of the same kind hiding inside the computation of np?

• Here is a mathematical property that might help you see one:np = n * np-1 (for p > 0)

8 October 2013 OSU CSE 53

Can you write the code for power as specified earlier, based on this property? (You also need to account for p = 0.)

Another Hidden Smaller Problem

• Here is a different mathematical property that might help you see a different smaller problem of the same kind:np = (np/2)2 (for even p > 1)

8 October 2013 OSU CSE 54

Another Hidden Smaller Problem

• Here is a different mathematical property that might help you see a different smaller problem of the same kind:np = (np/2)2 (for even p > 1)

8 October 2013 OSU CSE 55

Another Hidden Smaller Problem

• Here is a different mathematical property that might help you see a different smaller problem of the same kind:np = (np/2)2 (for even p > 1)

8 October 2013 OSU CSE 56

Can you write the code for power as specified earlier, based on this property?

(You also need to account for all the other values of p.)

Fast Powering

• If you can write the code by using the second property as a guide, your implementation will be much faster than by using the first property– And much faster than the obvious iterative

code!• This really matters when you adapt the

algorithm to work with NaturalNumberrather than int

8 October 2013 OSU CSE 57

Remaining Steps• Use FreeLunch when you need to solve a

smaller problem of the same kind (making sure it really is smaller in some sense!)

• Show that your code is correct assumingFreeLunch.power has the same contract as the power code you’re writing

• Replace any calls to FreeLunch.power with recursive calls to your own version of power

• Sit back and let the theorem about recursion show that your now-recursive code is correct

8 October 2013 OSU CSE 58