Transcript Slides

Introduction to Recursion
and
Recursive Algorithms
CS2110
1
Different Views of Recursion
Recursive Definition: n! = n * (n-1)!
(non-math examples are common too)
Recursive Procedure: a procedure that calls
itself.
Recursive Data Structure: a data structure
that contains a pointer to an instance of
itself:
public class ListNode {
Object nodeItem;
ListNode next, previous;
…
}
2
Recursion in Algorithms
• Recursion is a technique that is useful
– for defining relationships, and
– for designing algorithms that implement those
relationships.
• Recursion is a natural way to express
many algorithms.
• For recursive data-structures, recursive
algorithms are a natural choice
3
What Is Recursion?
•
A Definition Is Recursive If It Is Defined In
Terms Of Itself
– We use them in grammar school e.g.
what is a noun phrase?
•
a noun
•
an adjective followed by a noun phrase
– Descendants
•
the person’s children
•
all the children’s descendants
4
What Is Recursion?
• Think self-referential definition
• A definition is recursive if it is defined in
terms of itself
– Exponentiation - x raised to the y power
• if y is 0, then 1
• otherwise
it’s x * (x raised to the y-1 power)
5
Other recursive definitions in
mathematics
• Factorial:
n! = n (n-1)! and 0! = 1! = 1
• Fibonacci numbers:
F(0) = F(1) = 1
F(n) = F(n-1) + F(n-2) for n > 1
• Note base case
– Definition can’t be completely self-referential
– Must eventually come down to something
that’s solved “directly”
6
I know the steps needed to write a
simple recursive method in Java
1.
2.
3.
4.
Strongly Agree
Agree
Disagree
Strongly Disagree
7
Recursive Factorial
public static int factorial (int n) {
if (n == 1)
return 1;
else
return n * factorial(n-1);
}
• Exercise: trace execution (show method calls) for
n=5
8
Why Do Recursive Methods Work?
Activation Records on the Run-time Stack are the
key:
• Each time you call a function (any function) you get a
new activation record.
• Each activation record contains a copy of all local
variables and parameters for that invocation.
• The activation record remains on the stack until the
function returns, then it is destroyed.
Try yourself: use your IDE’s debugger and put a
breakpoint in the recursive algorithm
Look at the call-stack.
9
Broken Recursive Factorial
public static int Brokenfactorial(int n){
int x = Brokenfactorial(n-1);
if (n == 1)
return 1;
else
return n * x;
}
• What’s wrong here? Trace calls “by hand”
– BrFact(2) -> BrFact(1) -> BrFact(0) -> BrFact(-1)
-> BrFact(-2) -> …
– Problem: we do the recursive call first before
checking for the base case
– Never stops! Like an infinite loop!
10
Recursive Design
Recursive methods/functions require:
1) One or more (non-recursive) base cases
that will cause the recursion to end.
if (n == 1) return 1;
2) One or more recursive cases that
operate on smaller problems and get you
closer to the base case.
return n * factorial(n-1);
Note: The base case(s) should always be
checked before the recursive call.
11
Rules for Recursive Algorithms
• Base case - must have a way to end the
recursion
• Recursive call - must change at least
one of the parameters and make
progress towards the base case
– exponentiation (x,y)
• base: if y is 0 then return 1
• recursive: else return
exponentiation(x,y-1))
(multiply
x
times
12
How to Think/Design with Recursion
• Many people have a hard time writing
recursive algorithms
• The key: focus only at the current
“stage” of the recursion
– Handle the base case, then…
– Decide what recursive-calls need to be
made
• Assume they work (as if by magic)
– Determine how to use these calls’ results
13
Example: List Processing
• Is an item in a list? First, get a
reference current to the first node
– (Base case) If current is null, return false
– (Base case #2) If the first item equals the
target, return true
– (Recursive case – might be on the
remainder of the list)
• current = current.next
• return result of recursive call on new current
14
Next: Trees and Grammars
• Lab on binary tree data structures
• Maybe: HW5 on grammars
• Lecture Later: Recursion vs. iteration
– Which to choose?
– Does it matter?
• Maybe Later: recursion as an design
strategy
15
Next: Time Complexity and
Recursion
• Recursion vs. iteration
– Which to choose?
– Does it matter?
• Later: recursion as an design strategy
16
Recursion vs. Iteration
Interesting fact: Any recursive
algorithm can be re-written as an
iterative algorithm (loops)
• Not all programming languages support
recursion: COBOL, early FORTRAN.
• Some programming languages rely on
recursion heavily: LISP, Prolog,
Scheme.
17
To Recurse or Not To Recurse?
• Recursive solutions often seem elegant
• Sometimes recursion is an efficient design
strategy
• But sometimes it’s definitely not
– Important! we can define recursively and
implement non-recursively
– Many recursive algorithms can be re-written nonrecursively
• Use an explicit stack
• Remove tail-recursion (compilers often do this for you)
18
To Recurse or Not to Recurse?
• Sorting
– Selection sort vs. mergesort – which to choose?
• Factorial
– Could just write a loop.
– Any advantage to the recursive version?
• Binary search
– We’ll see two versions. Which to choose?
• Fibonacci
– Let’s consider Fibonacci carefully…
19
Recursive Fibonacci method
• This is elegant code, no?
long fib(int n) {
if ( n == 0 ) return 1;
if ( n == 1 ) return 1;
return fib(n-1) + fib(n-2);
}
• Let’s start to trace it for fib(6)
20
Trace of fib(5)
• For fib(5), we call fib(4) and fib(3)
– For fib(4), we call fib(3) and fib(2)
• For fib(3), we call fib(2) and fib(1)
– For fib(2), we call fib(1) and fib(0). Base cases!
– fib(1). Base case!
• For fib(2), we call fib(1) and fib(0). Base cases!
– For fib(3), we call fib(2) and fib(1)
• For fib(2), we call fib(1) and fib(0). Base cases!
• fib(1). Base case!
21
Fibonacci: recursion is a bad choice
• Note that subproblems (like fib(2) )
repeat, and solved again and again
– We don’t remember that we’ve solved
one of our subproblems before
– For this problem, better to store partial
solutions instead of recalculating values
repeatedly
– Turns out to have exponential timecomplexity!
22
Non-recursive Fibonacci
• Two bottom-up iterative solutions:
– Create an array of size n, and fill with
values starting from 1 and going up to n
– Or, have a loop from small values going
up, but
• only remember two previous Fibonacci values
• use them to compute the next one
• (See next slide)
23
Iterative Fibonacci
long fib(int n) {
if ( n < 2 ) return 1;
long answer;
long prevFib=1, prev2Fib=1; // fib(0) & fib(1)
for (int k = 2; k <= n; ++k) {
answer = prevFib + prev2Fib;
prev2Fib = prevFib;
prevFib = answer;
}
return answer;
}
24
Next: Putting Recursion to Work
• Divide and Conquer design strategy
– Its form
– Examples:
• Binary Search
• Merge Sort
– Time complexity issues
25
Divide and Conquer
• It is often easier to solve several small
instances of a problem than one large one.
– divide the problem into smaller instances of the
same problem
– solve (conquer) the smaller instances recursively
– combine the solutions to obtain the solution for
original input
– Must be able to solve one or more small inputs
directly
• This is an algorithm design strategy
– Computer scientists learn many more of these
26
General Strategy for Div. & Conq.
Solve (an input I)
n = size(I)
if (n <= smallsize)
solution = directlySolve(I);
else
divide I into I1, …, Ik.
for each i in {1, …, k}
Si = solve(Ii);
solution = combine(S1, …, Sk);
return solution;
27
Why Divide and Conquer?
• Sometimes it’s the simplest approach
• Divide and Conquer is often more efficient
than “obvious” approaches
– E.g. BinarySearch vs. Sequential Search
– E.g. Mergesort, Quicksort vs. SelectionSort
• But, not necessarily efficient
– Might be the same or worse than another
approach
• We must analyze each algorithm's time
complexity
• Note: divide and conquer may or may not be
implemented recursively
28
Top-Down Strategy
• Divide and Conquer algorithms illustrate a
top-down strategy
– Given a large problem, identify and break into
smaller subproblems
– Solve those, and combine results
• Most recursive algorithms work this way
• The alternative? Bottom-up
– Identify and solve smallest subproblems first
– Combine to get larger solutions until solve entire
problem
29
Binary Search of a Sorted Array
first
mid
last
• Strategy
– Find the midpoint of the array
– Is target equal to the item at midpoint?
– If smaller, look in the first half
– If larger, look in second half
30
Binary Search (non-recursive)
int binSearch ( array[], target) {
int first = 0; int last = array.length-1;
while ( first <= last ) {
mid = (first + last) / 2;
if ( target == array[mid] ) return mid; // found it
else if ( target < array[mid] ) // must be in 1st half
last = mid -1;
else // must be in 2nd half
first = mid + 1
}
return -1; // only got here if not found above
31
}
Binary Search (recursive)
int binSearch ( array[], first, last, target) {
if ( first <= last ) {
mid = (first + last) / 2;
if ( target == array[mid] ) // found it!
return mid;
else if ( target < array[mid] ) // must be in 1st half
return binSearch( array, first, mid-1, target);
else // must be in 2nd half
return binSearch(array, mid+1, last, target);
}
return -1;
}
• No loop! Recursive calls takes its place
– But don’t think about that if it confuses you!
• Base cases checked first? (Why? Zero items? One item?)
32
Mergesort is Classic Divide & Conquer
33
Algorithm: Mergesort
• Specification:
– Input: Array E and indexes first, and Last, such that
the elements E[i] are defined for first <= i <= last.
– Output: E[first], …, E[last] is sorted rearrangement of
the same elements
• Algorithm:
void mergeSort(Element[] E, int first, int last){
if (first < last) {
int mid = (first+last)/2;
mergeSort(E, first, mid);
mergeSort(E, mid+1, last);
merge(E, first, mid, last); // merge 2 sorted halves
}
}
34
Exercise: Trace Mergesort
Execution
• Can you trace MergeSort() on this list?
A = {8, 3, 2, 9, 7, 1, 5, 4};
35
Efficiency of Mergesort
• Wait for CS2150 and CS4102 to study
efficiency of this and other recursive algorithms
• But…
– It is more efficient that other sorts like
Selection Sort, Bubble Sort, Insertion Sort
– It’s O(n lg n) which is the same order-class as the
most efficient sorts (also quicksort and heapsort)
• The point is that the D&C approach matters
here, and a recursive definition and
implementation is a “win”
36
Merging Sorted Sequences
• Problem:
– Given two sequences A and B sorted in nondecreasing order, merge them to create one
sorted sequence C
– Input size: C has n items, and A and B have n/2
• Strategy:
– Determine the first item in C: it should be the
smaller of the first item in A and the first in B.
– Suppose it is the first item of A. Copy that to C.
– Then continue merging B with “rest of A” (without
the item copied to C). Repeat!
37
Algorithm: Merge
merge(A, B, C)
if (A is empty)
append what’s left in B to C
else if (B is empty)
append what’s left in A to C
else if (first item in A <= first item in B)
append first item in A to C
merge (rest of A, B, C)
else // first item in B is smaller
append first item in B to C
merge (A, rest of B, C)
return
38
Summary of Recursion Concepts
• Recursion is a natural way to solve
many problems
– Sometimes it’s a clever way to solve a
problem that is not clearly recursive
• Sometimes recursion produces an
efficient solution (e.g. mergesort)
– Sometimes it doesn’t (e.g. fibonacci)
• To use recursion or not is a design
decision for your “toolbox”
39
Recursion: Design and
Implementation
• “The Rules”
– Identify one or more base (simple) cases that can
be solved without recursion
• In your code, handle these first!!!
– Determine what recursive call(s) are needed for
which subproblems
– Also, how to use results to solve the larger
problem
– Hint: At this step, don’t think about how recursive
calls process smaller inputs! Just assume they
work!
40
Exercise: Find Max and Min
• Given a list of elements, find both the
maximum element and the minimum element
• Obvious solution:
– Consider first element to be max
– Consider first element to be min
– Scan linearly from 2nd to last, and update if
something larger then max or if something smaller
than min
• Class exercise:
– Write a recursive function that solves this using
divide and conquer.
• Prototype: void maxmin (list, first, last, max, min);
• Base case(s)? Subproblems? How to combine results?
41
What’s Next?
• Recursive Data Structures
– Binary trees
• Representation
• Recursive algorithms
– Binary Search Trees
• Binary Trees with constraints
• Parallel Programming using Threads
42