1 / 42

# Introduction to Recursion and Recursive Algorithms - PowerPoint PPT Presentation

Introduction to Recursion and Recursive Algorithms. CS2110. Different Views of Recursion. Recursive Definition : n! = n * (n-1)! (non-math examples are common too) Recursive Procedure : a procedure that calls itself.

I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.

## PowerPoint Slideshow about 'Introduction to Recursion and Recursive Algorithms' - lavada

An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.

- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript

### Introduction to Recursion andRecursive Algorithms

CS2110

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;

}

• 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

• 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

• 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

• otherwiseit’s x * (x raised to the y-1 power)

• 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”

• Strongly Agree

• Agree

• Disagree

• Strongly Disagree

Recursive Factorial in Java

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

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.

Broken Recursive Factorial in Java

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!

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.

• 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 (multiply x times exponentiation(x,y-1))

• 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

Example: List Processing in Java

• 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

Next: Trees and Grammars in Java

• 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

• Recursion vs. iteration

• Which to choose?

• Does it matter?

• Later: recursion as an design strategy

Recursion vs. Iteration in Java

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.

• 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 non-recursively

• Use an explicit stack

• Remove tail-recursion (compilers often do this for you)

• 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…

Recursive Fibonacci method in Java

• 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)

Trace of fib(5) in Java

• 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!

• 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 time-complexity!

Non-recursive Fibonacci in Java

• 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)

Iterative Fibonacci in Java

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;

}

}

• Divide and Conquer design strategy

• Its form

• Examples:

• Binary Search

• Merge Sort

• Time complexity issues

Divide and Conquer in Java

• 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

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;

Why Divide and Conquer? in Java

• 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

Top-Down Strategy in Java

• 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

first in Java

mid

last

Binary Search of a Sorted Array

• 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

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

}

Binary Search (recursive) in Java

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?)

Algorithm: Mergesort in Java

• 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

}

}

• Can you trace MergeSort() on this list?A = {8, 3, 2, 9, 7, 1, 5, 4};

Efficiency of Mergesort in Java

• Wait for CS2150 and CS4102 to study efficiency of this and other recursive algorithms

• But…

• It is more efficient that other sorts likeSelection 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”

Merging Sorted Sequences in Java

• Problem:

• Given two sequences A and B sorted in non-decreasing 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!

Algorithm: Merge in Java

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

• 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”

• “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!

Exercise: Find Max and Min in Java

• 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?

What in Java’s Next?

• Recursive Data Structures

• Binary trees

• Representation

• Recursive algorithms

• Binary Search Trees

• Binary Trees with constraints

• Parallel Programming using Threads