C++ Plus Data Structures: Nell Dale

Download as pdf or txt
Download as pdf or txt
You are on page 1of 39

C++ Plus Data Structures

Nell Dale
Chapter 7
Programming with Recursion

Slides by Sylvia Sorkin, Community College of Baltimore County - Essex Campus

1
Recursive Function Call
 A recursive call is a function call in
which the called function is the same as
the one making the call.

 In other words, recursion occurs when a


function calls itself!

 We must avoid making an infinite


sequence of function calls (infinite
recursion).
2
Finding a Recursive Solution
 Each successive recursive call should bring
you closer to a situation in which the answer is
known.
 A case for which the answer is known (and
can be expressed without recursion) is called
a base case.

 Each recursive algorithm must have at least


one base case, as well as the general
(recursive) case
3
General format for
many recursive functions

if (some condition for which answer is known)


// base case
solution statement

else // general case

recursive function call

SOME EXAMPLES . . .
4
Writing a recursive function to find
n factorial
DISCUSSION

The function call Factorial(4) should have value


24, because that is 4 * 3 * 2 * 1 .

For a situation in which the answer is known, the


value of 0! is 1.

So our base case could be along the lines of

if ( number == 0 )
return 1;
5
Writing a recursive function to find
Factorial(n)
Now for the general case . . .

The value of Factorial(n) can be written as


n * the product of the numbers from (n - 1) to 1,
that is,
n * (n - 1) * . . . * 1

or, n * Factorial(n - 1)

And notice that the recursive call Factorial(n - 1)


gets us “closer” to the base case of Factorial(0).
6
Recursive Solution

int Factorial ( int number )


// Pre: number is assigned and number >= 0.
{
if ( number == 0) // base case
return 1 ;
else // general case

return number + Factorial ( number - 1 ) ;


}

7
Three-Question Method of verifying
recursive functions

 Base-Case Question: Is there a nonrecursive way


out of the function?

 Smaller-Caller Question: Does each recursive


function call involve a smaller case of the original
problem leading to the base case?

 General-Case Question: Assuming each


recursive call works correctly, does the whole
function work correctly?

8
Another example where
recursion comes naturally
 From mathematics, we know that
20 = 1 and 25 = 2 * 24

 In general,
x0 = 1 and xn = x * xn-1
for integer x, and integer n > 0.

 Here we are defining xn recursively, in


terms of xn-1

9
// Recursive definition of power function

int Power ( int x, int n )


// Pre: n >= 0. x, n are not both zero
// Post: Function value = x raised to the power n.
{
if ( n == 0 )

return 1; // base case

else // general case

return ( x * Power ( x , n-1 ) ) ;

Of course, an alternative would have been to use looping


instead of a recursive call in the function body. 10
struct ListType

struct ListType
{
int length ; // number of elements in the list

int info[ MAX_ITEMS ] ;

};

ListType list ;

11
Recursive function to determine if
value is in list
PROTOTYPE
bool ValueInList( ListType list , int value , int startIndex ) ;

74 36 ... 95 75 29 47 ...
list[0] [1] [startIndex] [length -1]

index
Already searched of Needs to be searched
current
element
to
examine 12
bool ValueInList ( ListType list , int value , int startIndex )

// Searches list for value between positions startIndex


// and list.length-1
// Pre: list.info[ startIndex ] . . list.info[ list.length - 1 ]
// contain values to be searched
// Post: Function value =
// ( value exists in list.info[ startIndex ] . . list.info[ list.length - 1 ] )
{
if ( list.info[startIndex] == value ) // one base case
return true ;
else if (startIndex == list.length -1 ) // another base case
return false ;
else // general case
return ValueInList( list, value, startIndex + 1 ) ;
}
13
“Why use recursion?”

Those examples could have been written


without recursion, using iteration instead.
The iterative solution uses a loop, and the
recursive solution uses an if statement.

However, for certain problems the recursive


solution is the most natural solution. This often
occurs when pointer variables are used.

14
struct ListType
struct NodeType
{
int info ;
NodeType* next ;
}

class SortedType {
public :

. . . // member function prototypes

private :
NodeType* listData ;
};
15
RevPrint(listData);

listData

A B C D E

FIRST, print out this section of list, backwards

THEN, print
this element

16
Base Case and General Case
A base case may be a solution in terms of a “smaller” list.
Certainly for a list with 0 elements, there is no more
processing to do.

Our general case needs to bring us closer to the base case


situation. That is, the number of list elements to be
processed decreases by 1 with each recursive call. By
printing one element in the general case, and also
processing the smaller remaining list, we will eventually
reach the situation where 0 list elements are left to be
processed.

In the general case, we will print the elements of the smaller


remaining list in reverse order, and then print the current
pointed to element.
17
Using recursion with a linked list
void RevPrint ( NodeType* listPtr )

// Pre: listPtr points to an element of a list.


// Post: all elements of list pointed to by listPtr have been printed
// out in reverse order.
{
if ( listPtr != NULL ) // general case
{
RevPrint ( listPtr-> next ) ; // process the rest
cout << listPtr->info << endl ; // then print this element

}
// Base case : if the list is empty, do nothing
} 18
Function BinarySearch( )

 BinarySearch takes sorted array info, and


two subscripts, fromLoc and toLoc, and
item as arguments. It returns false if item is
not found in the elements
info[fromLoc…toLoc]. Otherwise, it
returns true.

 BinarySearch can be written using iteration,


or using recursion.

19
found = BinarySearch(info, 25, 0, 14 );

item fromLoc toLoc


indexes

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

info
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28

16 18 20 22 24 26 28

24 26 28

24
NOTE: denotes element examined

20
// Recursive definition
template<class ItemType>
bool BinarySearch ( ItemType info[ ] , ItemType item ,
int fromLoc , int toLoc )
// Pre: info [ fromLoc . . toLoc ] sorted in ascending order
// Post: Function value = ( item in info [ fromLoc . . toLoc] )
{ int mid ;
if ( fromLoc > toLoc ) // base case -- not found
return false ;
else {
mid = ( fromLoc + toLoc ) / 2 ;
if ( info [ mid ] == item ) // base case-- found at mid
return true ;
else if ( item < info [ mid ] ) // search lower half
return BinarySearch ( info, item, fromLoc, mid-1 ) ;
else // search upper half
return BinarySearch( info, item, mid + 1, toLoc ) ;
}
21
}
When a function is called...

 A transfer of control occurs from the calling


block to the code of the function. It is necessary
that there be a return to the correct place in the
calling block after the function code is executed.
This correct place is called the return address.

 When any function is called, the run-time stack


is used. On this stack is placed an activation
record (stack frame) for the function call.

22
Stack Activation Frames
 The activation record stores the return address
for this function call, and also the parameters,
local variables, and the function’s return value,
if non-void.

 The activation record for a particular function


call is popped off the run-time stack when the
final closing brace in the function code is
reached, or when a return statement is reached
in the function code.

 At this time the function’s return value, if non-


void, is brought back to the calling block return
address for use there. 23
// Another recursive function

int Func ( int a, int b )


// Pre: a and b have been assigned values
// Post: Function value = ??
{
int result;
if ( b == 0 ) // base case
result = 0;
else if ( b > 0 ) // first general case
result = a + Func ( a , b - 1 ) ) ; // instruction 50
else // second general case
result = Func ( - a , - b ) ; // instruction 70
return result;
} 24
Run-Time Stack Activation Records
x = Func(5, 2); // original call is instruction 100

FCTVAL ? original call


result ? at instruction 100
b 2 pushes on this record
a 5
for Func(5,2)
Return Address 100
25
Run-Time Stack Activation Records
x = Func(5, 2); // original call at instruction 100

FCTVAL ? call in Func(5,2) code


result ? at instruction 50
b 1 pushes on this record
a 5 for Func(5,1)
Return Address 50
FCTVAL ?
result 5+Func(5,1) = ?
b 2 record for Func(5,2)
a 5
Return Address 100
26
Run-Time Stack Activation Records
x = Func(5, 2); // original call at instruction 100

FCTVAL ?
call in Func(5,1) code
result ? at instruction 50
b 0 pushes on this record
a 5 for Func(5,0)
Return Address 50
FCTVAL ?
result 5+Func(5,0) = ?
b 1 record for Func(5,1)
a 5
Return Address 50
FCTVAL ?
result 5+Func(5,1) = ?
b 2 record for Func(5,2)
a 5
Return Address 100
27
Run-Time Stack Activation Records
x = Func(5, 2); // original call at instruction 100

FCTVAL 0
result 0
b 0 record for Func(5,0)
a 5
is popped first
Return Address 50
with its FCTVAL
FCTVAL ?
result 5+Func(5,0) = ?
b 1 record for Func(5,1)
a 5
Return Address 50
FCTVAL ?
result 5+Func(5,1) = ?
b 2 record for Func(5,2)
a 5
Return Address 100
28
Run-Time Stack Activation Records
x = Func(5, 2); // original call at instruction 100

FCTVAL 5
result 5+Func(5,0) = 5+ 0
b 1 record for Func(5,1)
a 5
is popped next
Return Address 50
with its FCTVAL
FCTVAL ?
result 5+Func(5,1) = ?
b 2 record for Func(5,2)
a 5
Return Address 100
29
Run-Time Stack Activation Records
x = Func(5, 2); // original call at line 100

FCTVAL 10
result 5+Func(5,1) = 5+5
b 2 record for Func(5,2)
a 5
is popped last
Return Address 100
with its FCTVAL 30
Show Activation Records for
these calls

x = Func( - 5, - 3 );

x = Func( 5, - 3 );

What operation does Func(a, b) simulate?

31
Tail Recursion
 The case in which a function
contains only a single recursive call
and it is the last statement to be
executed in the function.

 Tail recursion can be replaced by


iteration to remove recursion from
the solution as in the next example.

32
// USES TAIL RECURSION
bool ValueInList ( ListType list , int value , int startIndex )

// Searches list for value between positions startIndex


// and list.length-1
// Pre: list.info[ startIndex ] . . list.info[ list.length - 1 ]
// contain values to be searched
// Post: Function value =
// ( value exists in list.info[ startIndex ] . . list.info[ list.length - 1 ] )
{
if ( list.info[startIndex] == value ) // one base case
return true ;
else if (startIndex == list.length -1 ) // another base case
return false ;
else // general case
return ValueInList( list, value, startIndex + 1 ) ;
}
33
// ITERATIVE SOLUTION
bool ValueInList ( ListType list , int value , int startIndex )

// Searches list for value between positions startIndex


// and list.length-1
// Pre: list.info[ startIndex ] . . list.info[ list.length - 1 ]
// contain values to be searched
// Post: Function value =
// ( value exists in list.info[ startIndex ] . . list.info[ list.length - 1 ] )
{ bool found = false ;
while ( !found && startIndex < list.length )
{ if ( value == list.info[ startIndex ] )
found = true ;
else startIndex++ ;
}
return found ;
} 34
Use a recursive solution when:
 The depth of recursive calls is relatively
“shallow” compared to the size of the problem.

 The recursive version does about the same


amount of work as the nonrecursive version.

 The recursive version is shorter and simpler than


the nonrecursive solution.

SHALLOW DEPTH EFFICIENCY CLARITY

35
Using quick sort algorithm

A..Z

A..L M..Z

A..F G..L M..R S..Z

36
Before call to function Split

splitVal = 9
GOAL: place splitVal in its proper position with
all values less than or equal to splitVal on its left
and all larger values on its right

9 20 6 10 14 3 60 11
values[first] [last]

37
After call to function Split

splitVal = 9
smaller values larger values

6 3 9 10 14 20 60 11

values[first] [splitPoint] [last]

38
// Recursive quick sort algorithm

template <class ItemType >


void QuickSort ( ItemType values[ ] , int first , int last )
// Pre: first <= last
// Post: Sorts array values[ first. .last ] into ascending order
{
if ( first < last ) // general case
{ int splitPoint ;
Split ( values, first, last, splitPoint ) ;
// values [ first ] . . values[splitPoint - 1 ] <= splitVal
// values [ splitPoint ] = splitVal
// values [ splitPoint + 1 ] . . values[ last ] > splitVal
QuickSort( values, first, splitPoint - 1 ) ;
QuickSort( values, splitPoint + 1, last );
}
} 39

You might also like