think recursively.

advertisement

RECURSION (taken from Java Concepts, Cay Horstmann)

The method of recursion is a powerful technique to break up complx computational problems into simpler ones. The term “recursion” refers to the fact that the same computation recurs, or occurs repeatedly, as the problem is solved.

Recursion is often the most natural way of thinking about a problem, and there are some computations that are very difficult to perform without recursion. This material following shows you siple and complex examples of recursion and teaches you how to “think recursively.”

Example 1: Let’s begin with a simple example. Suppose we’d like to compute the area of a triangle with width n, assuming that each [] square has area 1. This value is sometimes called the nth triangle number. For example, as you can tell from looking at

[]

[] []

[] [] [] the third triangle number is 6.

Although you may know that there is a very simple formula for computing these numbers, remember the ultimate purpose of this section is not to compute triangle numbers, but to learn about the concept of recursion in a simple situation.

Here is an outline of the class that we will develop: public class Triangle

{ private int width;

public Triangle(int awidth)

{

width = awidth;

}

public int getArea()

{

………

}

}

If the width of the triangle is 1, then the triangle consists of a single square, and its area is 1. Let’s take care of this case first.

public int getArea() {

if(width == 1) return 1;

}

To deal with the general case, we consider the picture

[]

[] []

[] [] []

[] [] [] []

Suppose we knew the area of the smaller, colored triange. Then we could easily compute the area of the larger triangle as

smallerArea + width

How can we get the smaller area? Let’s make a smaller triangle and ask it!

Triangle smallerTriangle = new Triangle(width – 1);

int smallerArea = smallerTriangle.getArea();

Now we can complete the getArea method;

public in getArea() {

if(width==1)return 1;

Triangle smaller Triangle = new Triangle(width – 1)

int smallerArea = smallerTriangle.getArea();

return smallerArea + width;

}

A recursive computation solves a problem by using the solution of the same problem with simpler values.

Here is an illustration of what happens when we compute the area of a triangle with width 4.

The getArea method makes a smaller triangle of width 3

It calls getArea on that triangle.

 That method makes a smaller triangle of width 2.

It calls getArea on that triangle.

That method makes a smaller triangle of width 1.

It calls getArea on that triangle.

That method returns 1.

The method returns smallerArea + width = 1 + 2 = 3.

The method returns smallerARea + width = 3 + 3 += 6

 The method returns smallerArea + width = 6 + 4 = 10.

There are two key requirements to make sure that the recursion is successful.

1.

Every recursive call must simplify the computation in some way.

2.

There must be special cases to handle the simplest computations directly.

Now you give it a try… write a class and implement a recursive computation to find the first 15 terms of the recursive sequence given by: 𝑎(𝑛) = 2𝑎(𝑛 − 1) − 3 , 𝑎(0) = 4

Example 2: Permutations. We will now turn to a more complex example of recursion that would be difficult to program with a simple loop. We will design a class that lists all permutations of a string. A permutation is asimply a rearrangement of the letters. For example, the string “eat” has six permutations (including the original string itself): eat, eta, aet, ate, tea, tae.

As in the preceding example, we will define a class that is in charge of computing the answer. In the icase, the answer is not a single number but a collection of permuted strings. Here is our class: publc class PermutationGenerator

{

public PermutationGenerator(String aword) { …… }

ArrayList <String> getPermutations() { ……..}

}

Here is the test program that prints out all permutations of the string “eat” import java.util.ArrayList;

/**

This program tests the permutation generator

**/ public class PermutationGeneratorTester {

public static void main (String [ ] args) {

PermutationGenerator generator = new PermutationGenerator(“eat”);

ArrayList<String> permutations = generator.getPermutations();

for(String s: permutations)

{

System.out.println(s);

}

}

}

Now we need a way to generate the permutations recursively.

Lets think of fixing the first letter “e” and generating the permutations of the substring “at”. Then we’ll fix “a” as the first letter and get the permutations of “et”. Lastly, we’ll fix “t” as the first letter and generate the permutations of “ea”.

That’s the idea. The implementation is fairly straightforward. In the getPermutations method, we loop through all positions in the word to be permuted. For each of them, we compute the shorter word that is obtained by removing the ith letter:

String shorterWord = word.substring(0,i) + word.substring(I + 1)

We construct a permtation generator to get the permutations of the shorter word, and ask it to give us all permutations fot he shorter word.

PermutationGenerator shorterPermustationGenerator = new PermutationGenerator(shorterWord);

ArrayList <String> shorterWrodPermusations = shorterPermutationGenerator.getPermutations():

Finally, we add the removed letter to the front of all permutations of the shorter word.

for (String s : shorterWordPermutations)

{

result.add(word.charAt(i) + s);

}

As always, we have to provide a special case for the simplest strings. The simplest possible string is the empty string, which has a single permutation – itself.

Her is the complete PermutationGenerator class. import java.util.ArrayList;

/**

This class generates permutations of a word

*/ public class PermutationGenerator

{

private String word;

/** Constructs a permutation generator.

@apram aword the word to permute

*/ public PermutationsGenerator(String aword)

{

word = aword;

}

/** Gets all permutations of a given word */ public ArrayList <String> getPermutations()

{

ArrayList <String> result = new ArrayList<String>();

//The empty string has a single permutation: itself if(word.length() == 0)

{ result.add(word); return.result;

}

// Loop through all character positions for(int I = 0; i< word.length(); i++)

{

//Form a simpler word by removing the ith character

String shorterWord = word.substrin(0,i) + word.substring(i+1);

//Generate all permutations of the simpler word

PermutationGenerator shorterPermutationGenerator = new PermutationGenerator(shorterWord);

ArrayList<String> shorterWordPermutations = shorterPermutationGenerator.getPermutations();

//Add the removed character to the fron of each permutation of the stimper word for(Strings : shorterWordPermutations)

{

result.add(word.charAt(i) + s);

}

}

//Return all permutations

return result;

}

What are all the permutations of the four-letter word BEAT? How about REPEAT?

THINKING RECURSIVELY. To solve a problem recursively requires a different mindset than to solve it by programming loops. In fact, it helps if you are, or pretend to be, a bit lazy and like others to do most of te work for you. (Palindrome problem here for those of you who want it – I have it for 3 different levels of coding)

Step 1: Consider various ways to simplify inputs.

Step 2. Combine solutions with simper inputs into a solution of the original problem.

Step 3. Find solutions to the simplest inputs.

Step 4. Implement the solution by combining the simple cases and the reduction step

EFFICIENCY: As you have seen recursion can be a powerful tool to implement complex algorithms. On the other hand, recursion an lead to algorithms that perform poorly. So the question is: when is recursion beneficial and when is it inefficient?

Consider the Fibonacci sequence 𝑓(1) = 1 𝑓(2) = 1 𝑓(𝑛) = 𝑓(𝑛 − 1) + 𝑓(𝑛 − 2)

This is an example of when the recursive solution is much slower than the iterative (loop) one. Occasionally a recursive solution runs much slower than its iterative counterpart. In most cases, however, the recurseive solution is only slightly slower.

TRY THIS. You can compute the factorial function either with a loop, using the definition that 𝑛! = 𝑛(𝑛 − 1)(𝑛 − 2) … (2)(1) or recursively, using the definition that 0! = 1 and 𝑛! = (𝑛 − 1)! × 𝑛 . Is the recursive approach inefficient in this case? (Code them both and we’ll discuss this in class).

SUMMARY:

A RECURSIVE COMPUTATION SOLVES A PROBLEM BY USING THE SOLUTION OF THE SAME

PROBLEM WITH SIMPLER VALUES.

 FOR RECURSION TO TERMINATE, THERE MUST BE SPECIAL CASES FOR THE SIMPLEST VALUES.

SOMETIMES IT IS EASIR TO FIND A RECURSIVE SOLUTION IF YOU NAKE A SLIGHT CHANGE TO

THE ORIGINAL PROBLEM.

 OCCASIONALLY, A RECURSIVE SOLUTION RUNS MUCH SLOWER THAN ITS ITERATIVE COUNTER-

APRT. HOWVER IN MOST CASES THE RECURSIVE SOLUTION IS ONLY SLIGHTLY SLOWER.

IN MANY CASES, A REVURSIVE SOLUTION IS EASIER TO UNDERSTND AND IMPLEMENT

CORRECTLY THAN AN ITERATIVE SOLUTION.

Download