Download week6-recursion

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Functional decomposition wikipedia , lookup

Computability theory wikipedia , lookup

Algorithm characterizations wikipedia , lookup

Factorial wikipedia , lookup

Transcript
RECURSION
RECURSION
Objectives:
• become familiar with the idea of recursion
• Examine examples to show the wide variety of
situations to which recursion can be applied.
–
–
–
–
triangular numbers
factorials,
recursive binary search,
the Towers of Hanoi puzzle.
• strengths and weaknesses of recursion
RECURSION
– A method of
programming
whereby a
function directly
or indirectly calls
itself
– Problems: stop
recursion?
Designing Algorithms
• There is no single recipe for inventing algorithms
• There are basic rules:
– Understand your problem well – may require much mathematical
analysis!
– Use existing algorithms (reduction) or algorithmic ideas
• There is a single basic algorithmic technique:
Divide and Conquer
• In its simplest (and most useful) form it is simple induction
– In order to solve a problem, solve a similar problem of smaller size
• The key conceptual idea:
– Think only about how to use the smaller solution to get the larger one
– Do not worry about how to solve the smaller problem (it will be solved
using an even smaller one)
Recursion
• A recursive method is a method that contains
a call to itself
• Technically:
– All modern computing languages allow writing methods
that call themselves
• Conceptually:
– This allows programming in a style that reflects divide-nconquer algorithmic thinking
– At the beginning recursive programs are confusing – after a
while they become clearer than non-recursive variants
Recursion
Example:
Triangular Numbers
• 1, 3, 6, 10, 15, 21, …
• What is the next member of this series?
• The nth term in the series is obtained by adding n to the
previous term.
• The numbers in this series are called triangular numbers
because they can be visualized as a triangular arrangement of
objects
Recursion
int triangle(int n)
{
if(n==1)
return 1;
else
return( n + triangle(n-1) );
}
• The condition that leads to a recursive method returning
without making another recursive call is referred to as the
base case.
• It’s critical that every recursive method have a base case to
prevent infinite recursion and the consequent demise of the
program.
Recursive Methods
Must Eventually Terminate
A recursive method must have
at least one base, or stopping, case.
• A base case does not execute a recursive call
– stops the recursion
• Each successive call to itself must be a "smaller version
of itself”
– an argument that describes a smaller problem
– a base case is eventually reached
Key Components of a Recursive Algorithm
Design
1. What is a smaller identical problem(s)?
l Decomposition
2. How are the answers to smaller problems combined to form
the answer to the larger problem?
l Composition
3. Which is the smallest problem that can be solved easily
(without further decomposition)?
l Base/stopping case
RECURSION
Factorial (N!)
• N! = (N-1)! * N [for N > 1]
• 1! = 1
• 3!
= 2! * 3
= (1! * 2) * 3
=1*2*3
• Recursive design:
– Decomposition: (N-1)!
– Composition: * N
– Base case: 1!
factorial Method
int factorial(int n)
{
int fact;
if (n > 1)
// recursive case (decomposition)
fact = factorial(n – 1) * n;
// composition
else
// base case
fact = 1;
return fact;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
int factorial(int 2)
{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
int factorial(int 2)
{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}
int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return fact;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
int factorial(int 2)
{
int fact;
if (n > 1)
fact = factorial(1) * 2;
else
fact = 1;
return fact;
}
int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return 1;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return fact;
}
int factorial(int 1)
{
int fact;
if (n > 1)
fact = factorial(n - 1) * n;
else
fact = 1;
return 1;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = factorial(2) * 3;
else
fact = 1;
return fact;
}
int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return 2;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = 2 * 3;
else
fact = 1;
return fact;
}
int factorial(int 2)
{
int fact;
if (n > 1)
fact = 1 * 2;
else
fact = 1;
return 2;
}
int factorial(int 3)
{
int fact;
if (n > 1)
fact = 2 * 3;
else
fact = 1;
return 6;
}
Execution Trace
(decomposition)
public static int factorial(int n)
{
int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
else // base case
fact = 1;
return fact;
}
factorial(4)
factorial(3)
4
public static int factorial(int n)
{
int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
else // base case
fact = 1;
return fact;
}
Execution Trace
(decomposition)
factorial(4)
factorial(3)
factorial(2)
4
3
public static int factorial(int n)
{
int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
else // base case
fact = 1;
return fact;
}
Execution Trace
(decomposition)
factorial(4)
factorial(3)
factorial(2)
factorial(1)
4
3
2
Execution Trace
(composition)
public static int factorial(int n)
{
int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
else // base case
fact = 1;
return fact;
}
factorial(4)
*
factorial(3)
*
factorial(2)
3
*
factorial(1)->1
4
2
Execution Trace
(composition)
public static int factorial(int n)
{
int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
else // base case
fact = 1;
return fact;
}
factorial(4)
*
factorial(3)
*
factorial(2)->2
4
3
Execution Trace
(composition)
public static int factorial(int n)
{
int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
else // base case
fact = 1;
return fact;
}
factorial(4)
*
factorial(3)->6
4
Execution Trace
(composition)
public static int factorial(int n)
{
int fact;
if (n > 1) // recursive case (decomposition)
fact = factorial(n – 1) * n; (composition)
else // base case
fact = 1;
return fact;
}
factorial(4)->24
• This shows the pattern of function calls used to evaluate
Factorial(5) recursively
To evaluate Factorial(5)
evaluate 5 * Factorial(4)
To evaluate Factorial(4)
evaluate 4 * Factorial(3)
To evaluate Factorial(3)
evaluate 3 * Factorial(2)
Factorial(2) is 2
Return 2
Evaluate 3 * 2
Return 6
Evaluate 4 * 6
Return 24
Evaluate 5 * 24
Return 120
Remember:
Key to Successful Recursion
• if-else statement (or some other branching
statement)
• Some branches: recursive call
– "smaller" arguments or solve "smaller"
versions of the same task (decomposition)
– Combine the results (composition) [if
necessary]
• Other branches: no recursive calls
– stopping cases or base cases
Warning: Infinite Recursion May Cause
a Stack Overflow Error
• Infinite Recursion
– Problem not getting smaller (no/bad decomposition)
– Base case exists, but not reachable (bad base case
and/or decomposition)
– No base case
• Stack: keeps track of recursive calls by JVM (OS)
– Method begins: add data onto the stack
– Method ends: remove data from the stack
• Recursion never stops; stack eventually runs out of space
– Stack overflow error
look up a name in the phone book?
One Possible Way -
Binary Search
Search:
middle page = (first page + last page)/2
Go to middle page;
If (name is on middle page)
done; //this is the base case
else if (name is alphabetically before middle page)
last page = middle page //redefine search area to front half
Search //same process on reduced number of pages
else //name must be after middle page
first page = middle page //redefine search area to back half
Search //same process on reduced number of pages
Binary Search Algorithm
• Searching a list for a particular value
– sequential and binary are two common
algorithms
• Binary search:
– more efficient than sequential
– but the list must be sorted first!
• Why it is called binary each unsuccessful test for the target value
reduces the remaining search list by 1/2.
BINARY SEARCH: Recursion
private int search(int target, int first, int last)
{
int location = -1; // not found
if (first <= last) // range is not empty
{
int mid = (first + last)/2;
if (target == a[mid])
location = mid;
else if (target < a[mid]) // first half
location = search(target, first, mid - 1);
else //(target > a[mid]) second half
location = search(target, mid + 1, last);
}
return location;
}
Where is the composition?
• If no items
– not found (-1)
• Else if target is in the middle
– middle location
• Else
– location found by search(first half) or
search(second half)
Binary vs. Sequential Search
• Binary Search
– log2N + 1 comparisons (worst case)
• Sequential/Linear Search
– N comparisons (worst case)
• Binary Search is faster but
– array is assumed to be sorted beforehand
• Faster searching algorithms for “non-sorted arrays”
– More sophisticated data structures than arrays
– Later
Divide-and-Conquer Algorithms
• The recursive binary search is an example of the divide-and-conquer
approach.
• You divide the big problem into two smaller problems and solve each one
separately. The solution to each smaller problem is the same: You divide it
into two even smaller problems and solve them. The process continues
until you get to the base case, which can be solved easily, with no further
division into halves.
• The divide-and-conquer approach is commonly used with recursion,
although, you can also use a non-recursive approach.
• A divide-and-conquer approach usually involves a method that contains
two recursive calls to itself, one for each half of the problem.
• In the binary search, there are two such calls, but only one of them is
actually executed. (Which one depends on the value of the key.)
• The mergesort, which we’ll see later actually executes both recursive calls
(to sort two halves of an array).
RECURSION: Hanoi tower
RECURSION: Hanoi tower
• The Towers of Hanoi is an ancient puzzle consisting of a
number of disks placed on three columns, as shown above.
• The disks all have different diameters and holes in the middle
so they will fit over the columns. All the disks start out on
column A.
• The object of the puzzle is to transfer all the disks from
column A to column C.
• Only one disk can be moved at a time, and no disk can be
placed on a disk that’s smaller than itself.
• The Towers of Hanoi puzzle can be solved recursively by moving all but the
bottom disk of a subtree to an intermediate tower, moving the bottom
disk to the destination tower, and finally moving the subtree to the
destination.
The Value of Recursion
• Recursion can be used to replace loops.
• Recursively defined data structures, like lists, are very
well-suited to processing by recursive procedures
and functions
• A recursive procedure is mathematically more
elegant than one using loops.
• Sometimes procedures that would be tricky to write
using a loop are straightforward using recursion.
Recursion: Final Remarks
• The trick with recursion is to ensure that each
recursive call gets closer to a base case.
• Recursion can always be used instead of a loop.
(This is a mathematical fact.) In declarative
programming languages, like Prolog, there are no
loops. There is only recursion.
• Recursion is elegant and sometimes very handy, but
it is marginally less efficient than a loop, because of
the overhead associated with maintaining the stack.
Recursive Versus Iterative Methods
All recursive algorithms/methods
can be rewritten without recursion.
• Iterative methods use loops instead of recursion
• Iterative methods generally run faster and use less
memory--less overhead in keeping track of method
calls
So When Should You Use Recursion?
• Solutions/algorithms for some problems are inherently
recursive
– iterative implementation could be more complicated
• When efficiency is less important
– it might make the code easier to understand
• Bottom line is about:
– Algorithm design
– Tradeoff between readability and efficiency
Exercise 1: Recursion
• Convert number from H10->H2
7
1
2
3
1
2
1
2
1
0
Summary
• Recursive call: a method that calls itself
• Powerful for algorithm design at times
• Recursive algorithm design:
• Decomposition (smaller identical problems)
• Composition (combine results)
• Base case(s) (smallest problem, no recursive calls)
• Implementation
– Conditional (e.g. if) statements to separate different cases
– Avoid infinite recursion
• Problem is getting smaller (decomposition)
• Base case exists and reachable
– Composition could be tricky