Arrays in JAVA

advertisement
M180: Data Structures & Algorithms in Java
Recursion
Arab Open University
1
Recursive thinking
• Recursion: a programming technique in which a
method can call itself to solve a problem
• recursive definition: one which uses the concept being
defined in the definition itself
– In some situations, a recursive definition can be an
appropriate or elegant way to express a concept.
• Before applying recursion to programming, it is best to
practice thinking recursively
2
A recursive algorithm
• Consider the task of finding out what place you are in
a long line of people.
– If you cannot see the front of the line, you could ask the
person in front of you.
– To answer your question, this person could ask the person in
front of him/her, and so on.
3
A recursive algorithm
• Once the front person answers their place in line
(first), this information is handed back, one person
at a time, until it reaches you.
– This is the essence of recursive algorithms; many
invocations of the same method each solve a small part
of a large problem.
4
Infinite Recursion
• All recursive definitions have to have a non-recursive part
• If they didn't, there would be no way to terminate the
recursive path
• Such a definition would cause infinite recursion
• This problem is similar to an infinite loop, but the nonterminating "loop" is part of the definition itself
• The non-recursive part is often called the base case
Recursion
– Java's looping constructs make implementing this process
easy.
sum(1) = 1
sum(N) = N + sum(N - 1) if N > 1
– Consider what happens when the definition is applied to
the problem of calculating sum(4):
sum(4) = 4 + sum(3)
= 4 + 3 + sum(2)
= 4 + 3 + 2 + sum(1)
=4+3+2+1
– The fact that sum(1) is defined to be 1 without making
reference to further invocations of sum saves the process from
going on forever and the definition from being circular.
Factorial example
• The factorial for any positive integer N, written N!, is
defined to be the product of all integers between 1 and
N inclusive
n!  n  (n  1)  (n  2) ...1
// not recursive
public static long factorial(int n) {
long product = 1;
for (int i = 1; i <= n; i++) {
product *= i;
}
return product;}
7
Recursive factorial
• factorial can also be defined recursively:
n  1  n  f (n  1)
f ( n)  
n  0  1



• A factorial is defined in terms of another factorial
until the basic case of 0! is reached
// recursive
public static long factorial(int n) {
if (n == 0) {
return 1;
} else {
return n * factorial(n - 1);
}}
8
Recursive factorial
5!
120
5 * 4!
24
4 * 3!
6
3 * 2!
2
2 * 1!
1
Recursion vs. iteration
• every recursive solution has a corresponding iterative
solution
– For example, N ! can be calculated with a loop
• For some problems, recursive solutions are often more
simple and elegant than iterative solutions
• You must be able to determine when recursion is
appropriate
10
Recursive Programming
• Consider the problem of computing the sum of all the
numbers between 1 and any positive integer N
• This problem can be recursively defined as:
N
i
 N 
i 1
N 1
i

N  N 1 
i 1
 N  N 1  N  2 
N 3
i
i 1

N 2
i
i 1
Recursive Programming
// This method returns the sum of 1 to num
public int sum (int num)
{
int result;
if (num == 1)
result = 1;
else
result = num + sum (n-1);
return result;
}
Recursive Programming
result = 6
main
sum(3)
sum
result = 3
sum(2)
sum
result = 1
sum(1)
sum
Fibonacci Recursion
– Consider the definition of Fibonacci numbers below.
– The first and second numbers in the Fibonacci sequence are 1.
– Thereafter, each number is the sum of its two immediate
predecessors, as follows:
1 1 2 3 5 8 13 21 34 55 89 144 233 ...
Or in other words:
fibonacci(1) = 1
fibonacci(2) = 1
fibonacci(N) = fibonacci(N - 1) + fibonacci(N - 2) if N > 2
– This is a recursive definition, and it is hard to imagine how one
could express it no recursively.
Fibonacci Recursion
– As a second example of recursion, below is a method
that calculates Fibonacci numbers:
int fibonacci (int n){
if (n <= 2)
return 1;
else
return fibonacci (n - 1) +
fibonacci (n - 2);
}
Malformed Recursive Method
– Here is a subtler example of a malformed recursive
method:
int badMethod (int n){
if (n == 1)
return 1;
else
return n * badMethod(n - 2);
}
– This method works fine if n is odd, but when n is even, the
method passes through the stopping state and keeps on going.
Guidelines for Writing Recursive Methods
– A recursive method must have a well-defined termination
or stopping state.
– For the factorial method, this was expressed in the lines:
if (n == 1)
return 1;
– The recursive step, in which the method calls itself, must
eventually lead to the stopping state.
– For the factorial method, the recursive step was expressed
in the lines:
else
return n * factorial(n - 1);
Guidelines for Writing Recursive Methods
– Because each invocation of the factorial method is passed a
smaller value, eventually the stopping state must be reached.
– Had we accidentally written:
else
return n * factorial(n + 1);
– the method would describe an infinite recursion.
– Eventually, the user would notice and terminate the program,
or else the Java interpreter would run out memory, and the
program would terminate with a stack overflow error.
Number of Zeros in a Number
• Example: 2030 has 2 zeros
recursive
• If n has two or more digits
– the number of zeros is the number of zeros in n with the
last digit removed
– plus an additional 1 if the last digit is zero
• Examples:
– number of zeros in 20030 is number of zeros in 2003
plus 1
– number of zeros in 20031 is number of zeros in 2003
plus 0
numberOfZeros Recursive Design
• numberOfZeros in the number N
• K = number of digits in N
• Decomposition:
– numberOfZeros in the first K - 1 digits
– Last digit
• Composition:
– Add:
• numberOfZeros in the first K – 1 digits
• 1 if the last digit is zero
• Base case:
– N has one digit (K = 1)
numberOfZeros Method
public static int numberOfZeros(int n)
{
int zeroCount;
if (n==0)
zeroCount = 1;
else if (n < 10)
zeroCount = 0;
else if (n%10 == 0)
zeroCount = numberOfZeros(n/10) + 1;
else // n%10 != 0
zeroCount = numberOfZeros(n/10);
return zeroCount;
}
Which is (are)
the base
case(s)? Why?
Decompostion
, Why?
Composition,
why?
Execution Trace
(decomposition)
Each method
invocation will
execute one of the
if-else cases
shown at right.
public static int numberOfZeros(int n)
{
int zeroCount;
if (n==0)
zeroCount = 1;
else if (n < 10)
zeroCount = 0;
else if (n%10 == 0)
zeroCount = numberOfZeros(n/10) + 1;
else // n%10 != 0
zeroCount = numberOfZeros(n/10);
return zeroCount;
}
numberOfZeros(2005)
numberOfZeros(200)
numberOfZeros(20)
numberOfZeros(2)
5
0
0
Execution Trace
(composition)
Recursive calls
return
public static int numberOfZeros(int n)
{
int zeroCount;
if (n==0)
zeroCount = 1;
else if (n < 10)
zeroCount = 0;
else if (n%10 == 0)
zeroCount = numberOfZeros(n/10) + 1;
else // n%10 != 0
zeroCount = numberOfZeros(n/10);
return zeroCount;
}
numberOfZeros(2005)->2
+
numberOfZeros(200)->2
+
numberOfZeros(20)->1
numberOfZeros(2)->0
+
0->1
0->1
5->0
Download