Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 88

Chapter XXXVII

Recursion II

Chapter XXXVII Topics


37.1 Introduction

37.2 Pre-Recursion Assignment

37.3 Recursion Requires an Exit

37.4 Some Recursion Fundamentals

37.5 Recursion and Parameters

37.6 Fibonacci, a Recursive Nono

37.7 Evaluating Recursive Functions

37.8 Manipulating Parameters of Recursive Functions

37.9 Multiple Recursive Calls and the Tower of Hanoi

37.10 The Recursive Bubble Sort

37.11 Why Recursion?

37.12 The Merge Sort Case Study

37.13 Mutual Recursion

37.1 Introduction

Chapter XXXVII Recursion II 37.1


Near the end of the first year course a large chapter was devoted to the topic of
recursion. It was Chapter XXIII, one of the last chapters of Exposure C++, Part
II. A chapter or topic that is presented at, or near, the end of a course presents a
rather peculiar dilemma for instructors in a second year course. Can it be
assumed that the student learned this material? Was there perhaps a time crunch
and the chapter was covered quickly, or perhaps not at all? How many students at
the end of the year left for annual band, choir, orchestra trips? How many
students suffered from cerebral meltdown at the end of the year? Or more simply,
how many students were motivationally challenged in May? I believe there are
ample reasons to believe that not every student is fully equipped to plunge in and
use recursion with confidence right now.

You have also used very little recursion in the first part of the second year course
when you devoted most of the time to learn object oriented programming. So I
am going to do something unusual. The entire Chapter XXIII will be repeated
here followed by some advanced topics meant strictly for second year students.
The topic of recursion is vitally important for second year students and in a few
chapters you will be up to your ear lobes in recursive functions. I think it is wise
to simply start from scratch, assume nothing, and then move on to more advanced
topics. If you are comfortable with recursion, the majority of this chapter will go
very rapidly for you. If you never learned recursion or if you feel insecure, go
over this chapter very slowly and very methodologically.

Right now the only difference is this introduction. If the introduction was
identical to chapter XXIII, you may feel that the some mistake had been made
with file copying. Furthermore, the introduction in the first year indicates that
recursion will not be used a lot in the first year (nobrainer the year is just about
over) but the topic will be very important for the second year course. Do you still
remember the recursion definition?

Recursion Definition

Recursion is the computer science process, whereby a


function calls itself.

APCS Examination Alert

Chapter XXXVII Recursion II 37.2


Recursion is an important topic with frequent questions on
both the A and the AB APCS Examination.

The A Examination requires that a student can understand


and evaluate recursive functions. The A Examination
does not require that students generate free response
recursive solutions.

The AB Examination does require that students generate


some free response recursive solutions.

37.2 Pre-Recursion Assignment

This chapter will start in an unusual manner. A free response problem will be
explained shortly that is similar to a problem presented to students at one of the
earlier AP Computer science Examinations. The solution of this problem, called
Grid, is done surprisingly easy with the concepts explained in this chapter. In an
effort to prove a point you are presented with this problem before recursion is
explained.

Try to solve the Grid problem without using recursion. In other words, write a
solution to the problem using loop structures. Doing this exercise first, before we
look at recursion, will help to motivate why recursion is such a useful, and
powerful tool.

It is unlikely, but certainly possible, that you already understand recursion real
well, and that you are able to write a recursive solution to the Grid problem.
Doing so will defeat the purpose of this exercise. Play along and you will
understand soon enough why it is important to try this problem iteratively first.

The Grid Problem

Chapter XXXVII Recursion II 37.3


An image can be represented as a grid of black and white cells. Two cells in an
image are part of the same object if each is black and there is a sequence of moves
from one cell to the other, where each move is either horizontal or vertical to an
adjacent black cell. For example, the diagram below represents an image that
contains two objects, one of them consisting of a single cell. Black cells are
depicted with a small black diamond.


      
  
   
 
 
      
     
 

The following declarations are given:

enum CellType {Black,White};


typedef apmatrix <CellType> ImageType;

Write function AlterGrid with the following header such that all cells in the same
object as Grid[Row,Col] are set to White; otherwise Grid is unchanged.

void AlterGrid(ImageType Grid, int R, int C);

The instructions for the Grid problem are very similar to the instructions that were
given to students who took the 1987 AP Computer Science Examination. No
additional information, examples, hints, etc. will be given.

If you are a student at Berkner High School, and Mr. Schram is in a good mood,
you will receive extra credit for being able to write an iterative solution to the
Grid problem. You will receive negative credit for turning in a recursive
solution, which happens to be shown in this chapter, and proves that you do not
follow instructions.

Your solution needs to be printed, and it needs to be turned in before the first
official lecture on the recursion chapter. Give this problem some serious thought.
We will return to this problem at the end of the chapter, and then this exercise will
make more sense.
37.3 Recursion Requires an Exit

Chapter XXXVII Recursion II 37.4


The original BASIC programming language makes frequent use of the GOTO
control structure. GOTO is very much frowned upon by computer scientists who
do not like to see unconditional jumps in a program.

100 REM Program that counts integers starting at 0


110 K = 0
120 K = K + 1
130 PRINT K
140 GOTO 120

More than once you have wondered about Mr. Schram’s sanity. This sudden
switch to using BASIC is confirmation that Mr. Schram is not operating on all
thrusters. We can certainly program in C++ without using BASIC, but the
concept is nicely demonstrated in BASIC. The variable K is incremented by one,
displayed, and GOTO jumps back to line 120 to repeat the process. This process
will go on for quite some time because nothing has been done to stop the loop.
The GOTO statement specifies to go back to line 120 and repeat the process.
There is no conditional statement that stops the repetition. A similar result can be
achieved in C++ with recursion, as shown in program PROG3701.CPP below.

// PROG3701.CPP
// This program demonstrates recursion without an exit
#include <iostream.h>
#include <conio.h>
#include <iomanip.h>

int K;

void Count()
{
K++;
cout << setw(8) << K;
Count();
}

void main()
{
K = 0;
clrscr();
Count();
getch();
}

Chapter XXXVII Recursion II 37.5


PROG3701.CPP

1 2 3 5 5 6 7 8 9
10
11 12 13 14 15 16 17 18 19
20
21 35 23 24 25 26 27 28 29
30
31 32 33 34 35 36 37 38 39
40
41 42 43 44 45 46 47 48 49
50
51 52 53 54 55 56 57 58 59
60
61 62 63 64 65 66 67 68 69
70
71 72 73 74 75 76 77 78 79
80
81 82 83 84 85 86 87 88 89
90
91 92 93 94 95 96 97 98 99
100

Theoretically, this output will never stop and the numbers will keep increasing.
However, after the output continues for some time, the program will
freeze.
You may or may not see an error message.

This program will count numbers, incrementing by 1, just like the BASIC
program. It may seem that this odd kind of program will continue to count until
the power is turned off or the computer is rebooted. Function Count calls itself
every time that K is incremented and displayed. There appears to be nothing that
will stop this infinite counting process. But very soon the execution is halted with
or without an error message. The problem is that you have a stack overflow.

For the moment being, do not be concerned about what exactly a stack over flow
error is. Accept the fact that your program will crash with some type of problem.
It may be sooner; it may be later; but accept the fact that any recursive process
that is allowed to continue without a planned method of stopping the recursive
calls will crash the computer. Absolutely guaranteed.

Recursion simulates iteration. With iterative control structures, whether you are
using for, while or do...while, a condition is stated that stops the looping process.

Chapter XXXVII Recursion II 37.6


The same type of logic can be applied to any kind of recursive function. Prior to
making a recursive call, some condition needs to be checked first. If this
condition is true, then and only then, should the function call itself. Failure to
provide an exit out off the recursive process will make the program crash. Some
condition needs to be stated that checks the recursive calls.

One such example is shown with program PROG3702.CPP, which resembles the
previous program with a minor modification. A small if condition has been added
to stop the counting beyond 100.

// PROG3702.CPP
// This program demonstrates recursion with an exit

#include <iostream.h>
#include <conio.h>
#include <iomanip.h>

int K;

void Count()
{
K++;
cout << setw(8) << K;
if (K < 100)
Count();
}

void main()
{
K = 0;
Count();
}

PROG3702.CPP

1 2 3 5 5 6 7 8 9
10
11 12 13 14 15 16 17 18 19
20
21 35 23 24 25 26 27 28 29
30
31 32 33 34 35 36 37 38 39

Chapter XXXVII Recursion II 37.7


40
41 42 43 44 45 46 47 48 49
50
51 52 53 54 55 56 57 58 59
60
61 62 63 64 65 66 67 68 69
70
71 72 73 74 75 76 77 78 79
80
81 82 83 84 85 86 87 88 89
90
91 92 93 94 95 96 97 98 99
100

Important Recursion Rule

All recursive functions must have an exit that stops the


recursive process. The special case that stops the
recursive calls is called the base case.

Understanding the base case is the most significant part of understanding


recursion. Many program examples will follow that demonstrate recursion, and in
each case you will see that an exit or base case exists to control the execution.

37.4 Some Recursion Fundamentals

You were shown a very important recursion fundamental concept in the previous
section. Make sure that your recursive routine has an exit. There are various
other fundamental concepts about recursion that need to be understood before any
serious programming can involve recursion. These fundamentals will be explored
with the next three programs, which include short functions called Guess1,
Guess2 and Guess3.

Program PROG3703.CPP is the first example. Function Guess1 reads in


individual characters. The function stops making recursive calls when the letter
’O’ is entered. A cout statement follows immediately after each letter is entered,

Chapter XXXVII Recursion II 37.8


displaying the entered character. Keep in mind that the getch function does not
echo keyboard input to the monitor.

// PROG3703.CPP
// What is the output of GUESS1?

#include <iostream.h>
#include <conio.h>

void Guess1()
{
char Letter;
Letter = getch();
cout << Letter;
if (Letter != 'O')
Guess1();
}

void main()
{
clrscr();
cout << "Enter HELLO" << endl;
Guess1();
getch();
}

PROG3703.CPP OUTPUT

HELLO

You are probably not confused about the output of the previous program. Looks
simple enough. Enter a letter, and display a letter. Keep up this process until O is
entered and you are done. Enter Hello ... and what a surprise, the program
displays Hello.

The next program with Guess2 adds a new twist. The cout statement comes at
the end of the function. This means that the recursive call prevents the function
from completing its normal execution sequence. What exactly does this mean?

// PROG3704.CPP

Chapter XXXVII Recursion II 37.9


// What is the output of GUESS2?

#include <iostream.h>
#include <conio.h>

void Guess2()
{
char Letter;
Letter = getch();
if (Letter != 'O')
Guess2();
cout << Letter;
}

void main()
{
clrscr();
cout << "Enter HELLO" << endl;
Guess2();
getch();
}

PROG3704.CPP OUTPUT

OLLEH

What kind of dumb process takes HELLO as input and then displays OLLEH?
Students often expect that the output should be a single O letter. You may use the
logic that the cout statement is not reached until the base case is reached. When
O is finally entered, the condition statement is false, and the recursive calls stop.
Finally, cout can display something, and the character just entered is the letter O.
But where do the previous letters come from, and why are they displayed in
reverse order? The answer to that question will require a little detour.

The Internal Stack and Recursion

C++ has a special data structure called a stack that is hidden from the
programmer. Programmers cannot use the stack directly, but you can detect its
presence in a variety of ways. A stack is a data structure that will only access

Chapter XXXVII Recursion II 37.10


data from one end, usually called the top, in such a manner that it behaves like a
LIFO. Data is accessed Last In, First Out. C++ uses this stack, lurking
somewhere in the depths of the computer, to manage a variety of program
execution features. How exactly this stack manages its business, and how the
stack is implemented is of no concern to us. We simply accept the fact that the
stack exists somewhere.

NOTE
Do not confuse the internal stack with
the abstract stack data structure that
was introduced in Chapter XXXV.

So what is the job of this mysterious stack? Consider the following. Every
program statement is stored somewhere in computer memory, in machine code,
understandable only to the computer and people who like to read binary machine
code. Program execution sequence is handled by something called an Index
Pointer. The Index Pointer, or IP for short, contains the next memory address of
the line to be executed. In a simple program without any function calls,
consecutive program statements are usually consecutive memory addresses. The
value of the IP is increased by the memory size of the program statements.

Everything is fine with this sequence of program statements. Sooner or later a


function call is encountered. Now you know that a function can be called lots of
times and the code is only written once. This means that the executable code of a
function sits somewhere in memory, and the regular program execution needs to
jump to the function’s location whenever the function is called. There still is no
problem. Program execution cannot always follow consecutive memory
addresses. Jumps sometimes need to be made and the IP sees no problem.

Functions present a different situation. When the function execution is finished,


program execution needs to resume with the program statement following the
function call. Simple, just make sure that a memory address is provided at the
end of the function so that the IP gets a location to continue executing. Well it is
not all that simple. The same function gets called from many different program
locations. The start of the function is always at the same memory location.

Life is easy for the IP, but what address is used by the IP to resume execution.
That will be different each time that the same function is called. What comes to
the rescue is our friendly neighborhood stack. The stack is a temporary storage
structure that holds values in the unique LIFO manner. This system is made to
order to solve the function problem. When a function is called, the memory
location of the statement following the function call is added - called “pushed” -
to the stack. The stack now continues to do service by storing local variable
values of the function that are currently in scope. In other words, the stack takes

Chapter XXXVII Recursion II 37.11


care of the function we are executing. When the function is finished with its
values, there will be a conspicuous memory location left in the stack. This value
will find its way to the IP and program execution returns to the location where the
function was called.

The stack really demonstrates its LIFO business when functions call other
functions. Imagine that you start in the main function and call function First.
Somewhere in the middle of First, function Second is called. Now when Second
is finished where does it return? Should program execution continue back in the
main function or function First? Hopefully you realize that execution returns to
the location where the function was called. This means back to function First, and
then after First is finished, execution continues in the main function. Do you see
how neat the stack handle this business. First a main function address is pushed
on the stack, and then a First function address. The LIFO (Last In, First Out)
controls the execution perfectly.

Digest this stack business, and return to functions Guess1 and Guess2. Letter is
a local variable in both functions and its value will be stored on the temporary
stack that takes care of function business. Keep in mind that all function
information is stored on the stack until the function has finished executing.
Guess1 has no problems. A letter is entered and immediately displayed. Guess2
does not have a chance to display the letter value. A recursive call interrupts the
function execution. This interruption continues until the base case is reached.

void Guess1()
{
char Letter;
Letter = getch();
cout << Letter;
if (Letter != 'O')
Guess1();
}
void Guess2()
{
char Letter;
Letter = getch();
if (Letter != 'O')
Guess2();
cout << Letter;
}
Now comes the really critical part. The second function call leaves the first
function calls interrupted and unfinished. This means that critical information,
such as the value of Letter = ’H’ is kept on the stack. When the base case is
finally reached the letter ’O’ is displayed.

Chapter XXXVII Recursion II 37.12


Now the Index Pointer (IP) gets a value from the stack. It returns to the function
it just came from and needs to finish some business. What is unfinished? The
cout statement and what is the value of Letter? The last visit to Guess2 stored
the value ’L’ on the stack. All unfinished business is still on the stack. This is the
reason for the OLLEH output. The unfinished business is Last In - First Out.

You need to consider one more Guess function. The next program shows
Guess3, a function that performs the same action as the previous Guess functions,
but this time Letter is a reference parameter. Does this matter? It matters in the
sense that the value of Letter is not stored on the stack. The function execution
sequence will be controlled by the stack. The value of Letter will be controlled
by some memory location that is not part of the stack business.

// PROG3705.CPP
// What is the output of GUESS3?

#include <iostream.h>
#include <conio.h>

void Guess3(char &Letter)


{
Letter = getch();
if (Letter != 'O')
Guess3(Letter);
cout << Letter;
}

void main()
{
clrscr();
cout << "Enter HELLO" << endl;
char Letter;
Guess3(Letter);
getch();
}

What do you suspect will be the output of this program? Is it OLLEH like it was
before? Are there a few students left who think it may be just the single letter O?
Are you clueless and do not really know what is happening in this situation?

PROG3705.CPP OUTPUT

Chapter XXXVII Recursion II 37.13


OOOOO

How about five identical letters in a row. Weird? No, perfectly logical. Consider
this. Function Guess3 is called five times, which means that there must be five
cout output statements happening somewhere. These output statements need to
wait for the stack to take care of unfinished business. Like before, the stack
finishes each function call in LIFO order.

This time there is a new twist. The value of Letter is not preserved. The second
call to Guess3 enters a new value for Letter, and replaces its previous value.
When the base case (letter O) is reached, recursion stops, the letter O is displayed
and now four unfinished output statements display the value of Letter. At this
time Letter stores the character O, and the result is five identical O letter outputs.

Fundamental Recursion Concepts

All recursive functions require an exit or base case


that stops the recursive process.

The stack controls recursion, since it controls the


execution sequence of functions, and stores local
function information.

Every function call requires the completion of the called


function, even if the execution sequence is interrupted
by another recursive function call.

Incomplete recursive calls result in a LIFO execution


sequence.

37.5 Recursion and Parameters

Chapter XXXVII Recursion II 37.14


With some of the recursion basics behind us we can now focus on a wider variety
of recursive functions. Focus on parameters have been avoided on purpose, but
you will soon see that parameters play a very major role in recursion and
frequently it is parameter manipulation that makes recursion possible. In this
section you will be shown a variety of familiar functions that you have used with
loop structures. In each case the iterative function will be shown first, followed
by the recursive function. Only the functions will be shown in this chapter, with a
program heading that identifies the program that demonstrates the functions. The
actual program include both iterative and recursive functions, with one of the
functions commented out. You will need to alter the comments to test the
program both iteratively and recursively.

The Skip Function

The Skip function has served you well in many programs. Skip was used to
introduce functions with parameters. It will now be used to introduce the first
recursive function that uses parameters. But first take a close look at the iterative
Skip function.

// PROG3706.CPP
// Iterative Skip function

void Skip(int N)
{
int K;
for (K = 1; K <= N; K++)
cout << endl;
}

The parameter N, in the Skip function, dictates how many times cout << endl;
will be used. A for loop is used with N, determining how many times the loop
will be iterated. Skip is not exactly a complicated function, and some students
reading this section may feel insulted by the explanation of such a trivial piece of
programming code.

The main point here is to learn how to simulate iteration with recursion. Iterative
Skip uses a loop that repeats the cout statement, N times. Duplicating the

Chapter XXXVII Recursion II 37.15


iterative process with recursion means that N recursive calls need to be made.
This suggests that we use an identical function heading for the recursive function.

// PROG3706.CPP
// Recursive Skip function

void Skip(int N)
{
if (N > 0)
{
cout << endl;
Skip(N-1);
}
}

The recursive Skip function starts with protection against any values of N that are
less than or equal to 0. This condition also provides an exit for the function to
stop the recursive calls.

If the condition (N > 0) is true, one line is skipped with cout << endl; Repetition
is simulated by Skip calling itself. The recursive call is simple enough, but the
key factor is the parameter N-1. Each recursive call is made with a lesser value
for N. Eventually N is reduced to the base case.

The Greatest Common Factor (GCF) Function

Euclid’s algorithm for finding the GCF pops up at many computer science
locations, or at least it pops up a lot in Exposure C++. I really like this algorithm
and it demonstrates many concepts so nicely. Right now it will do an excellent
job demonstrating how parameter manipulation makes recursion both possible
and elegant. You thought that elegant applies to evening gowns? Think again, in
computer science a solution can also be elegant. I am not sure that I can define
elegant properly, but it appears that elegant solutions are very brief, straight
forward and usually not the first thing coming to the mind of an unsuspecting
computer science student. However, an elegant solution is frequently the most
natural and “obvious” solution to many computer science vetrans.

First, let us review the iterative GCF very rapidly. Remember the algorithm?
Refresh yourself quickly with this example.

Chapter XXXVII Recursion II 37.16


Find the GCF of N1 (111) and N2 (74).
Divide N1 (111) by N2 (74) and compute the remainder, which is 37.
The remainder is not zero; you are not finished.

N1 becomes N2
N2 becomes the remainder
Divide N1 (74) by N2 (37) and compute the remainder, which is 0.
Since the remainder is 0, you are finished, and the GCF is 37.

The iterative GCF function, below, implements Euclid’s algorithm with these
precise same steps. Trace through the program statements, and you will see that it
matches the description above So how is this done recursively?

// PROG3707.CPP
// Iterative GCF function

int GCF(int N1, int N2)


{
int Rem;
do
{
Rem = N1 % N2;
if (Rem == 0)
return N2;
else
{
N1 = N2;
N2 = Rem;
}
}
while (Rem != 0);
}

PROG3707.CPP OUTPUT

Enter the 1st number ===>> 111


Enter the 2nd number ===>> 74

GCF of 111 and 74 is 37

Chapter XXXVII Recursion II 37.17


// PROG3707.CPP
// Recursive GCF function

int GCF(int N1, int N2)


{
int Rem;
Rem = N1 % N2;
if (Rem == 0)
return N2;
else
return GCF(N2,Rem);
}

If elegant means shorter than you cannot argue that the recursive solution is more
elegant than the iterative variety. There are strong similarities in both solutions.
In each case, the Remainder of N1 and N2 is computed first. Also, in both cases
a conditional statement checks if the remainder is equal to zero, and if it is ... the
value of N2 is returned.

Now the logic stays the same but the implementation is quite different. The
recursive solution calls GCF when the base case is not satisfied. Keep in mind
that it is not sufficient to repeat the process. The algorithm requires that N1
becomes N2 and Remainder becomes N2.

Look how we can cleverly manipulate the parameters. The recursive function
call, return GCF(N2,Rem), passes the value of actual parameter N2, to formal
parameter N1. Also the value of actual parameter Rem is passed to formal
parameter N2.

The Factorial Function

The factorial of a number 6, usually denoted 6!, is computed by multiplying

6 x 5 x 4 x 3 x 2 x 1, which equals 720

The factorial function is often considered a classic example of recursion. This


may be true and the factorial function certainly demonstrates recursion very

Chapter XXXVII Recursion II 37.18


nicely, but other functions were introduced first, because they were easier to
understand.

// PROG3708.CPP
// Iterative Factorial function

long Factorial(int N)
{
long Temp = 1;
int K;
for (K=N; K>=1; K--)
Temp *= K;
return Temp;
}

The iterative function sets a local variable, Temp, to 1. This value will be
returned if the loop structure is not entered. If the loop is entered, Temp is
multiplied by the loop control variable from N down to 1. The loop can count,
and multiply in both directions, the backward counting was selected because it
resembles the way that we compute factorial more closely.

// PROG3708.CPP
// Recursive Factorial function

long Factorial(long N)
{
if (N == 0)
return 1;
else
return N * Factorial(N - 1);
}

Does this recursive function make any sense to you? Let us trace through an
example of computing 4! and see what happens. The function checks first to see
if parameter N is equal to 0, otherwise the function returns 4 times Factorial(3).

What is Factorial(3)? 3 times Factorial(2)


What is Factorial(2)? 2 times Factorial(1)
What is Factorial(1)? 1 times Factorial(0)

Chapter XXXVII Recursion II 37.19


What is Factorial(0)? The base case 1

Now back up to all the unfinished functions and complete the multiplication,
which is 1 x 2 x 3 x 4 = 24

PROG3708.CPP OUTPUT #1

Enter a positive integer ===>> 6

6! = 720

PROG3708.CPP OUTPUT #2

Enter a positive integer ===>> 10

10! = 3628800

37.6 Fibonacci, a Recursive Nono

The Fibonacci Sequence is a sequence of numbers that is formed by the sum of


the two previous numbers in the sequence. The sequence starts with two ones,
and from that point on, each number is formed by adding the two previous
numbers. The first 14 Fibonacci numbers are shown below, followed by sample
executions of the Fibonacci program for the 10th and 13th Fibonacci numbers.

1 1 2 3 5 8 13 21 34 55 89 144 233 377

PROG3709.CPP OUTPUT #1

Enter the desired Fibonacci number ===>> 10

Chapter XXXVII Recursion II 37.20


Fibonacci number #10 is 55

PROG3709.CPP OUTPUT #2

Enter the desired Fibonacci number ===>> 13

Fibonacci number #13 is 233

The complete program is shown with both the iterative and recursive function.
Does the logic of the functions make sense? The recursive solution certainly
appears to be much shorter. Now go ahead and test this program. You will be
surprised with the outcome of the recursive function. Why is it so slow? Try the
30th or 35th number and you will observe a noticeable delay.

// PROG3709.CPP
// Recursive Fibonacci function

#include <iostream.h>
#include <conio.h>

long Fibo(int N)
{
if ((N == 1) || (N == 2))
return 1;
else
return Fibo(N-1) + Fibo(N-2);
}

/
*******************************************************
*******
long Fibo(int N)
{
long Temp1 = 1;
long Temp2 = 1;
long Temp3;
int K;
for (K=3; K<=N; K++)
{
Temp3 = Temp1 + Temp2;
Temp1 = Temp2;
Temp2 = Temp3;
}
return Temp3;

Chapter XXXVII Recursion II 37.21


}
*******************************************************
********/

void main()
{
int Number;
long Result;
clrscr();
cout << "ENTER THE DESIRED FIBONACCI NUMBER ===>>
";
cin >> Number;
Result = Fibo(Number);
cout << endl << endl;
cout << "FIBONACCI NUMBER # " << Number
<< " IS " << Result << endl;
getch();
}

Many students new to recursion, mistakenly expect recursion to be faster than


iteration. I assume that this is based on the shorter code. Look at any example so
far and the recursive code is shorter than the iterative code. Perhaps it seems that
less code means less execution. Well you got fooled. The nature of program
execution efficiency is more complex.

First consider the general nature of recursion. It involves making multiple


function calls to simulate the looping process. Multiple function calls each
require a jump to another memory location and a large number of function calls
causes a penalty in execution time. Now this is the general explanation and it
accounts for a difference that is hardly noticeable. It does not explain the
Fibonacci delay that can be observed even with the fastest computers on the
market. Something else is going on.

Look at the recursive statement in the Fibonacci function. Do you see anything
that you did not notice in any of the previous examples? Do not say that the
variables have different names. Look at the nature of the recursive statements. Is
there something odd, unusual, that catches your eye?

return Fibo(N-1) + Fibo(N-2);

Chapter XXXVII Recursion II 37.22


Perhaps you do not get the point, so look at a set of other recursive statement from
two previous functions, and compare them. Is it more clear now?

return N * Factorial(N-1);

return GCF(N2,Rem);

Yes, you do see it, and you did not act like you did. The Fibonacci function
generates two recursive calls in one statement. There is only one call for any of
the other functions that we checked. Is this then bad? For small numbers this is
not bad, but the consequences of these double recursive calls are disastrous for a
large number. Each recursive statement doubles the number of recursive calls.
On the next page, we will demonstrate just calling the 9th number. Check and see
how many calls this ends up generating.

7 8

5 6 6
7

3 4 4 5 4 5 5
6

1 2 2 3 2 3 3 4 2 3 3 4 3 4
4 5

12 12 12 23 12 12 23 12 2
3 23 3 4

12 12 1
2 12 12 2 3

12

Chapter XXXVII Recursion II 37.23


The diagram is only for the 9th Fibonacci number. You see the large number of
recursive calls, and you can also see how many times the same computation is
performed.

The efficiency of the recursive Fibonacci algorithm is horrible. Each higher


number doubles the execution time. If the 30th number takes 1 second, the 31st
takes 2 seconds, the 32nd 4 seconds and the 50th number will take 2,097,152
seconds. We are not talking milliseconds here. This is some serious delay. The
pyramid diagram demonstrates how each level doubles the recursive calls of the
previous level. Near the bottom of the pyramid, the number of calls gets a little
raggedy, but that has to do with the fact that Fibo(1) and Fibo(2) both provide an
exit. The main point with the Fibonacci example is that double recursive calls
can rapidly get out of hand and yield an unacceptable time efficiency.

Recursion Warning

Avoid recursive functions that make a single program


statement with multiple recursive calls, like the Fibonacci
sequence.

return Fibo(N-1) + Fibo(N-2);

37.7 Evaluating Recursive Functions

So far this chapter has talked a lot about recursion and you have been shown a
variety of functions with iterative and recursive solutions. This information helps
to understand the nature of recursive routines.

You will also need to be able to evaluate recursive subprograms. Given some
function, can you determine what the value or values the functions return when
recursion is involved? This section gives you a variety of exercises to help you
gain practice and confidence in evaluating recursive functions.

In earlier chapters you were encouraged to use a variable trace table to keep
track of the variable values in a program. Consider the following iterative
function YoHo.

int YoHo(int O, int Q)

Chapter XXXVII Recursion II 37.24


{
int R;
do
{
R = P % Q;
if (R == 0)
return Q;
else
{
P = Q;
Q = R;
}
}
while (R != 0)
}

What is the value of YoHo(112,72)? Assume that you do not realize that function
YoHo computes the GCF of P and Q. A good approach is to use a variable trace
table. This table is nothing more than keeping track of variable values as the
program execution steps through the iterations.

P Q R YoHo Returns
112 72 40 N/A
72 40 32 N/A
40 32 8 N/A
32 8 0 8

Tracing through program executions is important with iteration, and it is equally


important with recursion. This section will present 14 exercise examples. Study
these examples carefully before you try to do any of the assigned chapter
exercises yourself.

Be careful that you do not stop after a few examples. This is not a group of
fourteen exercises of the same type. There is a considerable variety of problems
presented. Understanding this group of exercises is a good start in understanding
recursion.

APCS Examination Alert

Evaluating recursive functions, like the examples that

Chapter XXXVII Recursion II 37.25


follow will be required for both the A and AB multiple
choice part of the examination.

Few students are comfortable with this type of problem at the


first introduction. Most students achieve excellent evaluation
skills with repeated practice.

Chapter XXXVII Recursion II 37.26


Exercise 01

Determine that F1(5) = 25.

int F1(int N)
{
if (N == 1)
return 25;
else
return F1(N-1);
}
CALL # N Function F1 returns

1 5 F1(4)
2 4 F1(3)
3 3 F1(2)
4 2 F1(1)
5 1 25

Exercise 02

Determine that F2(5) causes a Stack Overflow error.

int F2(int N)
{
if (N == 1)
return 25;
else
return F2(N+1);
}
CALL # N Function F2 returns

1 5 F2(6)
2 6 F2(7)
3 7 F2(8)

Stack Overflow error because the Base Case cannot be reached.

Chapter XXXVII Recursion II 37.27


Exercise 03

Determine that F3(5) = 39

int F3(int N)
{
if (N == 1)
return 25;
else
return N + F3(N-1);
}
CALL # N Function F3 returns

1 5 5 + F3(4)
2 4 4 + F3(3)
3 3 3 + F3(2)
4 2 2 + F3(1)
5 1 25

F3(5) = 25 + 2 + 3 + 4 + 5 = 39

Exercise 04

Determine that F4(1) = 10

int F4(int N)
{
if (N == 5)
return 0;
else
return N + F4(N+1);
}
CALL # N Function F4 returns

1 1 1 + F4(2)
2 2 2 + F4(3)
3 3 3 + F4(4)
4 4 4 + F4(5)
5 5 0

F4(1) = 0 + 4 + 3 + 2 + 1 = 10

Chapter XXXVII Recursion II 37.28


Exercise 05

Determine that F5(6) = 38

int F5(int N)
{
if (N == 1 || N == 0)
return 0;
else
return N + F5(N-1) + F5(N-2);
}
CALL # N Function F5 returns
1 6 6 + F5(5) + F5(4)
2 5 5 + F5(4) + F5(3)
3 4 4 + F5(3) + F5(2)
4 3 3 + F5(2) + F5(1)
5 2 2 + F5(1) + F5(0)
6 1 0
F5(6) = 0 + 2 + 3 + 4 + 5 + 6 = 20

Using previous techniques, the answer appears to be 20. However, you are
expected to determine that F5(6) = 38. Something is wrong, and the problem
is that exercise 05 involves nasty double recursive calls. The tree structure below
determines the value that must be added at each function call. A call to F5(1) or
F5(0) will result in 0 being added.

6
5 4

4 3 3 2

3 2 2 1 2 1 1 0

2 1 1 0 1 0 1 0

1 0

Now we have 6 + 5 + 4 + 4 + 3 + 3 + 2 + 3 + 2 + 2 + 2 + 2 = 38

Chapter XXXVII Recursion II 37.29


Exercise 06

Determine that F6(5) = 120

int F6(int N)
{
if (N == 1)
return 1;
else
return N * F6(N-1);
}
CALL # N Function F6 returns
1 5 5 * F6(4)
2 4 4 * F6(3)
3 3 3 * F6(2)
4 2 2 * F6(1)
5 1 1

F6(5) = 1 * 2 * 3 * 4 * 5 = 120

Exercise 07

Determine that F7(5,6) = 30

int F7(int A, int B)


{
if (A == 0)
return 0;
else
return B + F7(A-1,B);
}
CALL # A B Function F7 returns
1 5 6 6 + F7(4,6)
2 4 6 6 + F7(3,6)
3 3 6 6 + F7(2,6)
4 2 6 6 + F7(1,6)
5 1 6 6 + F7(0,6)
6 0 6 0

F7(5,6) = 0 + 6 + 6 + 6 + 6 + 6 = 30

Chapter XXXVII Recursion II 37.30


Exercise 08

Determine that F8(4,3) = 81

int F8(int A, int B)


{
if (A == 0)
return 1;
else
return B * F8(A-1,B);
}
CALL # A B Function F8 returns
1 4 3 3 * F8(3,3)
2 3 3 3 * F8(2,3)
3 2 3 3 * F8(1,3)
4 1 3 3 * F8(0,3)
5 0 3 1

F8(4,3) = 1 * 3 * 3 * 3 * 3 = 81

Exercise 09

Determine that F9(4,3) = 64

int F9(int A, int B)


{
if (B == 0)
return 1;
else
return A * F9(A,B-1);
}
CALL # A B Function F9 returns
1 4 3 4 * F9(4,2)
2 4 2 4 * F9(4,1)
3 4 1 4 * F9(4,0)
4 4 0 1

F9(4,3) = 1 * 4 * 4 * 4 = 64

Chapter XXXVII Recursion II 37.31


Exercise 10

Determine that F10(7,3) = 17

int F10(int A, int B)


{
if (A < B)
return 5;
else
return B + F10(A-1,B+1);
}

CALL # A B Function F10 returns

1 7 3 3 + F10(6,4)
2 6 4 4 + F10(5,5)
3 5 5 5 + F10(4,6)
4 4 6 5

F10(7,3) = 5 + 5 + 4 + 3 = 17

Exercise 11

Determine that the output of F11 will be HOWDY when YDWOH is entered.

void F11()
{
char CH;
CH = getch();
if (CH != 'H')
F11();
cout << CH;
}
CALL # CH Function F5 outputs
1 Y Nothing, function not completed
2 D Nothing, function not completed
3 W Nothing, function not completed
4 O Nothing, function not completed
5 H H
F11 output ===>> HOWDY (LIFO function completion)

Chapter XXXVII Recursion II 37.32


Exercise 12

Determine that the output of F12 will be HELLOOLLEH when HELLO is


entered.

void F12()
{
char CH;
CH = getch();
cout << CH;
if (CH != ’O’)
F12();
cout << CH;
}
CALL # CH Function F5 outputs
1 H H, second cout not yet completed
2 E E, second cout not yet completed
3 L L, second cout not yet completed
4 L L, second cout not yet completed
5 O OO
F12 output ===>> HELLOOLLEH

Exercise 13

Determine that C = 8 when F13(112,72,C) is called.

void F13(int A, int B, int &C)


{
int D;
D = A % B;
if (D == 0)
C = B;
else
F13(B,D,C);
}
CALL # A B C Function F10 calls
1 112 72 N/A F13(72,40,C)
2 72 40 N/A F13(40,32,C)
3 40 32 N/A F13(32,8,C)

Chapter XXXVII Recursion II 37.33


4 32 8 8
C = 8 when F13(112,72,C) is finished

Exercise 14

Determine that C = 9 when F14(10,5,C) is called.

void F14(int A, int B, int &C)


{
if (A < B)
C = A + B;
else
F14(A-1,B,C);
}

CALL # A B C Function F10 calls


1 10 5 N/A F14(9,5,C)
2 9 5 N/A F14(8,5,C)
3 8 5 N/A F14(7,5,C)
4 7 5 N/A F14(6,5,C)
5 6 5 N/A F14(5,5,C)
6 5 5 N/A F14(4,5,C)
7 4 5 9 F14(4,5,C)
C = 9 when F14(10,5,C) is finished

37.8 Manipulating Parameters of


Recursive Functions

We are now going to move recursion up one notch on the difficulty ladder. All
the previous examples have been fairly straight forward and the comparisons
between iterative and recursive solutions always involved the same parameters. It
did not really seem to matter how the procedure or function heading was stated.

Chapter XXXVII Recursion II 37.34


In the past, recursion and iteration made a difference in the body of the function,
not the heading. This is now going to change and in this section you will see that
recursion may very well require that the parameter lists in the function headings
are altered to accommodate the recursion requirement. We shall use the Linear
and Binary Search to demonstrate this concept.

The Linear Search Function

First, we need to review the Linear Search function, implemented iteratively.


This implementation of the search function returns the index of the search item,
called Key or it returns Index = -1 if the search is not successful. On this page
the implementation for the LinearSearch function is not shown. Details will
follow on the next page.

// PROG3710.CPP
// Iterative Linear Search Function

#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#include "BOOL.H"
#include "APVECTOR.H"

typedef apvector <int> ListType;


void LinearSearch(const ListType &List, int Key, int
&Index);

void main()
{
clrscr();
ListType List(10);
int SearchNumber;
int Index;
for (int K = 0; K < 10; K++)
{
List[K] = random(9000) + 1000;
cout << List[K] << " ";
}
cout << endl << endl;
cout << "Enter search number ===>> ";
cin >> SearchNumber;
LinearSearch(List,SearchNumber,Index);
if (Index == -1)
cout << SearchNumber << " is not in the list" <<
endl;
else

Chapter XXXVII Recursion II 37.35


cout << SearchNumber << " is at index " << Index
<< endl;
getch();
}

PROG3711.CPP OUTPUT #1

1095 1035 4016 1299 4201 2954 5832 2761 7302


9549

Enter search number ===>> 4201


4201 is at index 4

PROG3711.CPP OUTPUT #2

1095 1035 4016 1299 4201 2954 5832 2761 7302


9549

Enter search number ===>> 5000


5000 is not in the list
You will not get an explanation of the LinearSearch function. You have seen it
before, and it is presented here to help recall the iterative logic and syntax. The
meaning of each one of the variables is shown below the function.

void LinearSearch(const ListType &List, int Key, int


&Index)
{
int N = List.length();
bool Found = false;
int K;
K = 0;
while (K < N && !Found)
{
if (List[K] == Key)
Found = true;
else
K++;
}
if (Found)
Index = K;
else
Index = -1;

Chapter XXXVII Recursion II 37.36


List is the array data structure of sorted integers
N is the quantity of elements in the List array
Key is the number being searched
Index is the location of Key, if found, otherwise -1 is returned
Found is a Boolean flag to exit the loop
K is the loop control variable and current search index

So why is there a need to alter the function heading for a recursive function? The
logic is with the loop control variable K. The variable K is incremented
throughout the while loop. When you change the loop structure and implement
recursion, the parameters List, Key and Index are still needed. And where
exactly is our friend K located? Using the same logic as the iterative function
cannot work because K is defined locally, and initialized to 0. This works fine for
a loop structure, but a recursive solution would constantly reset K to 0. The trick
is that K needs to become a parameter. This time only the recursive function is
shown. The driver program for the LinearSearch is the same, except for the
function call.

void LinearSearch(const ListType &List, int Key,


int
&Index, int K)
{
int N = List.length();
if (K > N-1)
Index = -1;
else
if (List[K] == Key)
Index = K;
else
LinearSearch(List,Key,Index,K+1);
}

The addition of the K parameter solves our problem very nicely. Each time that a
recursive call is made, the value of actual parameter K+1 is passed to formal
parameter K. This will achieve the desired incrementing of the array index.

Chapter XXXVII Recursion II 37.37


Since K is now a parameter, you will notice that the LinearSearch function does
not assign any initial value to K. This is not possible, since it would constantly
restart the index value. K still needs an initial value, and that is accomplished
with the first call to LinearSearch done in the program’s main function. One
actual parameter needs to be added to match K, and the constant value 0 works
very nicely. K starts as 0 and is ready now to search the list.

// PROG3711.CPP
// Recursive Linear Search Function

LinearSearch(List,SearchNumber,Index,0);

The Binary Search Function

The lessons that you learned with the recursive LinearSearch function will help
in solving the BinarySearch function. LinearSearcxh only required that one
parameter, K, is added to the function heading. You will find that BinarySearch
is going to be a little more complicated. But first review the logic of the iterative
function. The driving program is identical to the one used for the LinearSearch.

// PROG3712.CPP
// Iterative Binary Search Function

void BinarySearch(const ListType &List, int Key, int


&Index)
{
int N = List.length();
bool Found = false;
int Lo = 0;
int Hi = N-1;
int Mid;
while (Lo <= Hi && !Found)
{
Mid = (Lo + Hi) /2;
if (List[Mid] == Key)

Chapter XXXVII Recursion II 37.38


Found = true;
else
if (Key > List[Mid])
Lo = Mid+1;
else
Hi = Mid-1;
}
if (Found)
Index = Mid;
else
Index = -1;
}

This function has many variables. The following list helps to clarify the purpose
for each one of the parameters, and local variables.

List is the array data structure of sorted integers


N is the quantity of elements in the List array
Key is the number being searched
Index is the location of Key, if found, otherwise -1 is returned
Found is a Boolean flag to exit the loop
Lo is the lowest index of the search range
Hi is the highest index of the search range
Mid is the array index in the middle of the search range

PROG3712.CPP OUTPUT

1000 1100 1200 1300 1400 1500 1600 1700 1800


1900

Enter search number ===>> 1600


1600 is at index 6

The important feature of the binary search is the search range. In a linear search
the search range is the entire array. In a binary search the initial search range is
the entire array. The index of the first element of the first search is 0, and that
value is assigned to Lo. The index of the last element of the search range is N-1,
and that value is assigned to Hi. The binary search performs a comparison with
the element in the middle of the search range. Variable Mid is computed as the
mean of Lo and Hi. After each comparison, the search range is “chopped” in

Chapter XXXVII Recursion II 37.39


half. A new Lo or a new Hi must be determined if the desired item is not found.
This process is repeated until the search item is found or Lo becomes greater than
Hi.

The iterative solution defines Lo, Hi and Mid locally. Recursively, there is no
problem with using Mid locally. The value of Mid needs to be computed each
time that a new location is searched. However, Lo and Hi are a different story.
These index values change with each recursive call. Check the code below. Does
it make sense to you?

// PROG3713.CPP
// Recursive Binary Search Function

void BinarySearch(const ListType &List, int Key, int &Index,


int Lo, int Hi)
{
int Mid;
if (Lo > Hi)
Index = -1;
else
{
Mid = (Lo + Hi) /2;
if (List[Mid] == Key)
Index = Mid;
else
if (Key > List[Mid])
BinarySearch(List,Key,Index,Mid+1,Hi);
else
BinarySearch(List,Key,Index,Lo,Mid-1);
}
}

An initial call to BinarySearch, made from the main function uses a function call
with similar constant-value-logic like the LinearSearch fucntion.

BinarySearch(List,Key,Index,0,N-1);

If the search item, Key is not found and Key is greater than List[Mid], the lower
half of the list can be ignored. This means that the Hi value remains unchanged,
but we have a new Lo, which is one greater than Mid. This will result in the
recursive function call below.

Chapter XXXVII Recursion II 37.40


BinarySearch(List,Key,Index,Mid+1,Hi);

On the other hand, if Key is less than List[Mid], the upper half of the list can be
ignored. This means that the Lo value remains unchanged, and we have a new
Hi, which is one less than Mid. This will result in the recursivie function call
below.

BinarySearch(List,Key,Index,Lo,Mid-1);

Ponder over this question. In this section you were shown a linear search function
and a binary search function that are implemented recursively by adding
parameters to the function heading. Can you write these functions recursively
with the same exact parameter list as the iterative solutions?

Chapter XXXVII Recursion II 37.41


37.9 Multiple Recursive Calls and
The Tower of Hanoi

The Towers of Hanoi is a classic problem. This problem fits beautifully in a


recursion chapter because of the elegant (meaning trouble for the recursively
challenged) recursive solution to the problem and the lessons that are presented
in understanding recursion.

So what is the problem? Look at the pictures below and on the next page. Three
towers, or pegs are shown. The towers are labeled A, B and C. Tower A has five
numbered disks. The disks are stacked with the largest disk on the bottom. Your
problem is to move all the disks from tower A to tower C. There is a catch,
though. You can only move one disk at a time and you are not allowed to stack a
larger disk on top of a smaller disk. With the three pegs, it is possible to
accomplish this mission. The second peg can be used as an auxiliary peg.

Towers of Hanoi at the Start of the 5-Disk Problem

1 1
2 2
3 3
4 4
5 5
Peg A Peg B Peg C

Towers of Hanoi at the End of the 5-Disk Problem

1 1
2 2
3 3
4 4
5 5
Peg A Peg B Peg C

Chapter XXXVII Recursion II 37.42


The Trivial One-Disk Problem
Let us look at a variety of problems, starting with the simplest possible situation.
Suppose that you only need to move one disk from peg A to peg C. Would that
be any kind of problem?

1 1
Peg A Peg B Peg C

How do we solve the 1-Disk problem? In exactly one step that be described with
the single statement:

Move Disk1 from Peg A to Peg C

1 1
Peg A Peg B Peg C

The solution to the 1-Disk problem is commonly known as the trivial case. The
example was shown here not to insult your intelligence, but to make sure that you
focus on the trivial case. We will be seeking a recursive solution to this problem
and it may just turn out that the trivial case also is very much part of a solution
with more than one disk.

Ironically, students have the biggest problem in properly setting up the base case
of a recursive solution to a problem. Basically, this means to start by considering
what is necessary to get out.

The Easy Two-Disk Problem

Chapter XXXVII Recursion II 37.43


The 1-Disk problem was too easy. Let us move up to the big time and solve the
2-Disk version of the Tower of Hanoi. It will take three disk moves to move two
disks from peg A to peg C.

Step 1

1 1
2 2
Peg A Peg B Peg C

Step 2

2 2 1 1
Peg A Peg B Peg C

Step 3

1 1 2 2
Peg A Peg B Peg C

Step 4

Chapter XXXVII Recursion II 37.44


1 1
2 2
Peg A Peg B Peg C

The 2-Disk situation is also pretty easy to follow. It can be summed up with the
following three disk moves:

Move Disk1 from Peg A to Peg B


Move Disk2 from Peg A to Peg C
Move Disk1 from Peg B to Peg C

I will not bother you with all the pictures of a 3-Disk solution. (besides this really
is kind-of-a-pain to word process) Study the steps and see if they make sense to
you. If necessary, take some objects and simulate the Tower of Hanoi problem.
Here is the 3-Disk solution.

Move Disk1 from Peg A to Peg C


Move Disk2 from Peg A to Peg B
Move Disk1 from Peg B to Peg B
Move Disk3 from Peg B to Peg C
Move Disk1 from Peg A to Peg A
Move Disk2 from Peg A to Peg C
Move Disk1 from Peg B to Peg C

The complexity of this problem grows very rapidly as the number of disks grows.
The number of moves that are made by the solution also grows very rapidly. The
1-Disk problem takes 1 move. The 2-Disk problem takes 3 moves. The 3-Disk
problem takes 7 moves. And a 4-Disk problem takes 15 moves. Perhaps you
have recognized the pattern by now. The number of moves is 2N-1.

Tower of Hanoi Formula

There is a pattern in the number of minimum moves


to solve a given Tower of Hanoi problem.

Chapter XXXVII Recursion II 37.45


With N the number of disks to be moved, the formula
below computes the number or required moves.

2N - 1

It may seem like a very difficult task to write a program that solves the Towers of
Hanoi puzzle. It would not be so tricky for a fixed number of disks, like 4, but
you may be perplexed by a general program that will handle any given number of
disks.

The Towers of Hanoi program in this chapter solves the puzzle, and furthermore it
solves the puzzle with an absolute minimum of code. The code is recursive and it
will not be easy to understand at first or second glance.

You may see statements in various computer science text books with the
statement the correctness of the TowersOfHanoi program is easily established.
Such statements are intimidating to students who are confused by some bizarre
looking programming code. Take your time to digest the logic and syntax. Listen
to your teacher’s explanation and allow this recursive business to start soaking in.
You will need to be pretty drenched before you feel comfortable. But suddenly,
one day, it all starts to click and recursion will be your friend from then on.

On the next page is a program that solves this interesting puzzle. A sample
execution follows, to demonstrate the output of the program. Some explanation
will be given later about the logic of the program solution, but right now there is
another objective. Take a look at the solution. Never mind whether it makes
sense or not. Isn’t the function that moves the disks amazingly short.

If you do not think that the solution is short, get behind your computer and write a
solution that does not use recursion. It can be done iteratively and your teacher
will offer you some major extra credit if your iterative solution is shorter than the
recursive solution.

// PROG3714.CPP
// Recursive Tower of Hanoi function

#include <iostream.h>
#include <conio.h>

void SolveHanoi(char Source, char Temp, char Dest, int

Chapter XXXVII Recursion II 37.46


N)
{
if (N != 0)
{
SolveHanoi(Source,Dest,Temp,N-1);
cout << "Move Disk " << N << " From Peg "
<< Source << " to Peg " << Dest << endl;
SolveHanoi(Temp,Source,Dest,N-1);
}
}

void main()
{
int N;
clrscr();
cout << "Enter how many disks [1..8] ===> ";
cin >> N;
cout << endl << endl;
SolveHanoi('A','B','C',N);
getch();
}

PROG3714.CPP OUTPUT

Enter how many disks [1..8] ===>> 3

Move Disk 1 From Peg A to Peg C


Move Disk 2 From Peg A to Peg B
Move Disk 1 From Peg C to Peg B
Move Disk 3 From Peg A to Peg C
Move Disk 1 From Peg B to Peg A
Move Disk 2 From Peg B to Peg C
Move Disk 1 From Peg A to Peg C

You have noticed in the examples used in this chapter so far that recursive
solutions have been shorter than iterative solutions. For the most part, this has
fallen in the whoop..tee..doo category. The difference in code has not been
significant enough to be overly concerned. With this program you cannot
compare. Will an iterative solution be shown? No, it will not, since it is an extra
credit assignment. However, you can probably still appreciate that this
SolveHanoi function is very short, and accomplished a lot.

Chapter XXXVII Recursion II 37.47


How does this short function manage to do its job? The secret is in some double
recursive calls that have not been used before. Do not get confused and think that
this resembles the recursive Nono that you were warned about with the Fibonacci
function. With Fibonacci, two recursive calls were made in one program
statement. With SolveHanoi you observe two separate program statements, and
each one makes a recursive call.

So how do we make sense out of this. I will assume that you understand clearly
how to move a single disk. Now this simplest problem does not require a loop or
any type of recursion. This simplest situation not only occurs if you have a
problem with one disk, it also occurs in all the problems when all the disks are on
the destination peg, except for the last disk. This one-disk move shall be the
trivial case.

Now recursion requires a general statement that solves the problem for the
situation that is not the base case. For instance, with the Factorial function the
general solution for integer N is to multiply N * Factorial(N-1), provided N is
greater than 0. Can you think about the Tower of Hanoi in a general way? Do
not think about C++ program code right now. State the general steps required to
solve this puzzle.

Tower of Hanoi General Solution

Move N-1 disks from Source Peg to Auxiliary Peg

Move the Nth disk from Source Peg to Destination Peg

Move N-1 disks from Auxiliary Peg to Destination Peg

These three English program solution statements are very significant. The middle
statement sure sounds a bunch like the trivial case. Essentially, the statement says
that after you get rid of N-1 disks that are on top of the Nth disk, the Nth disk can
now easily be moved to its final destination. With the Nth disk in the correct
place, you can now move N-1 disks from the temporary peg to the final
destination on top of the Nth disk.. These three general steps are illustrated with
a diagram on the next page.

Chapter XXXVII Recursion II 37.48


Towers of Hanoi at the Start of the N-Disk Problem

1 1
2 2
N-2 N-2
N-1 N-1
N N
Peg A Peg B Peg C

Move N-1 Disks from Peg A to Peg B

1 1
2 2
N-2 N-2
N N N-1 N-1
Peg A Peg B Peg C

Move Nth Disk from Peg A to Peg C

1 1
2 2
N-2 N-2
N-1 N-1 N N
Peg A Peg B Peg C

Move N-1 Disks from Peg B to Peg C

Chapter XXXVII Recursion II 37.49


1 1
2 2
N-2 N-2
N-1 N-1
N N
Peg A Peg B Peg C

Moving N-1 disks from one peg to another peg should sound like a similar
solution required with one less disk. It also sounds like this process continues
until only one disk is left. Those students whose recursive-understanding-light-
bulbs have acquired a considerable glow are smelling serious recursion here. But
something else is going on. Every one of the recursive examples presented in this
chapter only had a single statement to make recursive calls. The Fibonacci
function did call two functions recursively, but it was in one statement.

Take a look at the SolveHanoi function again. You will recognize the three
English statements of the general solution in the C++ code. You will also note
that there are two separate statements that make recursive calls. Do not think that
this is unusual. There are many problems in computer science that are solved
with multiple recursive calls.

But there remains a significant problem to solve. If you simulate the Tower of
Hanoi puzzle with some disks, blocks, books or any other objects, you will
rapidly notice a pattern. The source peg and destination peg are constantly
changing. With the three pegs the initial aim is to move N disks from A to C.
But first you need to move N-1 disks from A to B. This means that for N-1 disks
the destination peg is now B. And later when these same disks have to be moved,
B will be the source peg.

This constantly changing of source and destination is the secret in the solution.
The parameter list of the SolveHanoi function shows four parameters. The first
three parameters are used to store the peg designation with characters A, B or C.
The fourth parameter, N, passes the number of disks to be moved.

void SolveHanoi(char Source, char Temp, char Dest, int


N)

Chapter XXXVII Recursion II 37.50


{
if (N != 0)
{
SolveHanoi(Source,Dest,Temp,N-1);
cout << "Move Disk " << N << " From Peg "
<< Source << " to Peg " << Dest << endl;
SolveHanoi(Temp,Source,Dest,N-1);
}
}

FUNCTION CALL IN THE MAIN FUNCTION

SolveHanoi('A','B','C',N);

Notice how the parameters are called Source, Temp(orary) and Dest(ination).
The initial call, made in the main function, passes A to Source, B to Temp and C
to Dest. This all seems to match what is desired.

Now our first job is to move N-1 disks from Source to Temp. Look at the first
recursive statement. Source is passed to Source, Dest is passed to Temp, and
Temp is now passed to Dest. Is this a confusing mess, or do you see what is
going on? The parameters of the recursive calls are constantly changing what is
Source, Temp and Dest.

Notice in the second recursive call that now Temp is passed to Source. This is
precisely what is needed, because you have N-1 disks sitting on the temporary
peg. After the Nth disk is moved to its proper location, it is time to move the N-1
disks, and they are located on the temporary peg.

Some students are not convinced. You do buy the movement of the single Nth
disk, but there seems to be some casual movement of N-1 disks going on. Is there
not a rule about moving one disk at a time? You are totally correct and totally
alert. After the Nth disk is in place we ignore that disk and ask how we can move
N-1 disks. This is done by first moving N-2 disks out of the way to a temporary
location, so that the (N-1th) disk can find its home. This process is continued
until the trivial case makes life simple by moving a single disk. What you have is
pure recursion.

Did we forget to consider how the recursion will stop? No, it has not been
forgotten. Function SolveHanoi can only generate recursive calls, and move a
disk whenever (N != 0) is true. Any time that the disk count become 0, it is time
to say good bye and return to complete unfinished business from previous
function calls.

Chapter XXXVII Recursion II 37.51


37.10 The Recursive Bubble Sort

The recursive Bubble Sort section may start a petition going on around
questioning the sanity of one “formerly-reasonably-informed” computer science
teacher. Experienced computer science teachers may suspect that Leon Schram
had one too many wipe-outs during a recent ski trip and cerebral damage
unfortunately seems to be of the permanent variety. Who in their right mind
would consider using BubbleSort with recursion of all things? This is doubly bad.
The Bubble Sort is pitiful to start with and doing it recursively it like taking a
really, really sorry tasting dinner and putting it in a micro wave several days later
for a leftover meal. Well relax.

Bubble Sort and Recursion

Please do not think that recursion is recommended for


the Bubble Sort algorithm.

This section investigates some special situation that


occurs with nested loops that are converted to recursion.

The Bubble Sort is a short, easy to understand, algorithm,


that uses nested loops. The question of nested loops
and recursion might as well be addressed with a familiar
algorithm.

The essence of the Bubble Sort algorithm is the comparison loop that compares
adjacent array elements and swaps elements, which are not properly ordered. At
the conclusion of the comparison loop, one element (smallest or largest) is in its
desired location. The comparison loop is nested inside an outer loop that is
designed to repeat the comparison passes until the list is sorted.

If we are only considering a single loop at this stage, our life is not that
complicated. After all, was the linear search algorithm not a case of a single
loop? Program PROG3715.CPP creates a random list of integers, displays the
random list, sorts the list with a partial Bubble Sort, and displays the list again.
With the partial sort you will only see the results of a single comparison pass.
This program is shown completely. After this program we will only focus on the
Bubble Sort routine.
// PROG3715.CPP

Chapter XXXVII Recursion II 37.52


// Recursive Bubble-Sort Algorithm
// This program only makes one comparison pass

#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#include <iomanip.h>
#include "APVECTOR.H"

typedef apvector <int> ListType;


void CreateList(ListType &List);
void DisplayList(const ListType &List);
void SortList(ListType &List, int Q);
void Swap(int &A, int &B);

void main()
{
clrscr();
ListType List(12);
CreateList(List);
DisplayList(List);
SortList(List,0);
DisplayList(List);
}

void CreateList(ListType &List)


{
int N = List.length();
int K;
for (K = 0; K < N; K++)
List[K] = random(9000) + 1000;
}

void DisplayList(const ListType &List)


{
cout << endl << endl;
int N = List.length();
int K;
for (K = 0; K < N; K++)
cout << setw(5) << List[K];
cout << endl;
getch();

Chapter XXXVII Recursion II 37.53


}

void Swap(int &A, int &B)


{
int T;
T = A; A = B; B = T;
}

void SortList(ListType &List, int Q)


{
int N = List.length();
if (Q < N-1)
{
if (List[Q] > List[Q+1])
Swap(List[Q],List[Q+1]);
SortList(List,Q+1);
}
}

PROG3715.CPP OUTPUT

1095 1035 4016 1299 4201 2954 5832 2761 7302 9549 3473
4998

1035 1095 1299 4016 2954 4201 2761 5832 7302 3473 4998
9549

The recursive SortList function shows a process that is starting to look familiar
by now. First you check if the index counter, Q, is still less than N-1. If that
condition is true, adjacent array elements are compared, possibly swapped, and a
recursive call repeats the process with the index incremented by 1.

Now you are about to follow into a potential trap. You feel that recursion works
just fine for the inner loop. A recursive call replaced the loop structure. Logic
should therefore dictate that another recursive call and another exit takes care of
the original outer loop of the bubble Sort routine.

Chapter XXXVII Recursion II 37.54


All of this is good logical thinking. So load program PROG3716.CPP and check
it out. You can also alter the previous program slightly with the function shown
below. There are now two recursive calls with two separate exits to control each
recursive statement. Looks good? Should work, right? You know by the tone of
this paragraph that it will not work. Try it, the program is going to give you
problems in the form of some infinite loop or other runtime error enjoyment.

// PROG3716.CPP
// Recursive Bubble-Sort Algorithm
// This program does not provide a proper exit

void SortList(ListType &List, int P, int Q)


{
int N = List.length();
if (P < N-1)
{
if (Q < N-P)
{
if (List[Q] > List[Q+1])
Swap(List[Q],List[Q+1]);
SortList(List,P,Q+1);
}
SortList(List,P+1,0);
}
}

So what is the problem? Cold booting in C++ is not that much fun, and if you
also have to wait for Windows 95 to start over after a cold boot, you might as well
order some pizza. There is a very distinct problem, and it is very subtle. It goes
straight to one of the fundamental rules of recursion.

Perhaps it will help to first look at the correct recursive function. You may need
to look twice before you see that the only difference is an innocent, and very
significant, else in the correct function.

Chapter XXXVII Recursion II 37.55


// PROG3717.CPP
// Correct Recursive Bubble-Sort Algorithm

void SortList(ListType &List, int P, int Q)


{
int N = List.length();
if (P < N-1)
{
if (Q < N-1)
{
if (List[Q] > List[Q+1])
Swap(List[Q],List[Q+1]);
SortList(List,P,Q+1);
}
else
SortList(List,P+1,0);
}
}

The subtlety of the problem has to do with unfinished function calls. It is your
aim to repeat the comparison pass N-P times. This is achieved by making a
recursive call, that increments Q each time. Fine, this works. Now you may be
tempted to say so Q is no longer the proper value, and the comparison pass stops.
This is precisely when you want to switch to a recursive call with P incremented.
This is exactly what you want to do, but without the else you get more than you
bargained for.

Think about it. Every time that you repeated the comparison pass, you left the
function unfinished. At some point you need to back up and clean up all the
unfinished business. Imagine that you made 50 recursive calls to complete one
comparison pass. Without the else you now have 49 unfinished functions to
return to and this means 49 recursive calls.

None of this business happens with the modest else statement. Here we perform
comparisons, complete with recursive calls, or we make a recursive call that
increments P. The else insures that one recursive call is made or the other call is
made. The bottom line is that you do not have a whole bunch of unwanted
recursive calls left to be finished.

Chapter XXXVII Recursion II 37.56


37.11 Why Recursion?

After all this recursion business we are going to finish with a very simple
question? Why do we use recursion? The question is short and simple, but the
answer is hardly easy.

Let us start with execution speed. You may suspect that recursion executes faster
than iteration. Speed is always desirable and that would certainly make recursion
popular if more speed is gained. Do you think that recursion is faster than
iteration? Sorry ... good guess, but recursion is actually slower than iteration.
The reason is that calling functions to execute a series of program statements
takes longer because it makes a jump away from the current memory sequence.
The time penalty for calling a function is extremely slight, but a large number of
function calls can make a difference. A function like the BubbleSort, which you
saw in the last section, has a very very large number of function calls. Remember
the Fibonacci Sequence problem. The excessive number of recursive calls slowed
the execution speed down tremendously.
Well if speed is not the reason for recursion then what might it be? How about
memory? All these recursive functions seem to be quite short, and certainly
shorter than the iterative function. Yes, it must be memory. Bad news again my
good friends. Recursion uses more memory. The smaller code of the recursive
function may occupy less space than code for an iterative function, but there are
other factors to consider. Our friendly stack stores local function information and
controls the execution sequence of function calls. Lots of recursive calls can
result in lots of unfinished business, which requires lots of memory to store until
the program execution returns to clean up.

You are now officially stumped. Two of the best reasons for doing something
different in a program, speed and memory have been eliminated from contention.
You do remember that program development is a compromise between speed,
memory and readability.

Can it be that recursion is more readable? This one is a little tricky. There are
plenty of people who will claim that recursion is natural and thus more readable.
There is a good argument to be made, but there are also plenty of students, new to
recursion, who will need some more exposure before all this recursive business is
natural to them. And whether or not recursion is readable to you does not matter
because it is not the reason for using recursion. You did have a preview of the
reason for using recursion when you looked at the Tower of Hanoi problem.

Chapter XXXVII Recursion II 37.57


The Reason for Using Recursion

Recursion is preferred over iteration when it is easier to


write program code recursively.

That is it? Yes my good students that is precisely the reason. You may think that
iteration is simpler than recursion, and it may very well be that way for functions
like Skip, GCF, Factorial and other small functions. Those function examples
worked well to demonstrate recursive techniques. In reality there is little need to
use a recursive GCF function or any of the others early function examples.

But think about the Tower of Hanoi. You were not shown how to do that problem
iteratively and the recursion did seem pretty tricky. Yes, I agree, that problem
was certainly tougher than the Factorial function. The point is that it is easier
recursively than it is iteratively. Try it. See if you can create an iterative solution
for the Tower of Hanoi problem.

The Grid Problem Revisited

At the beginning of this chapter you were challenged to solve a special problem in
an iterative manner. You may or may not have tried to solve this problem, but the
Grid problem is shown in this chapter as a classic example of a problem that is so
much easier to do recursively than iteratively. Did you forget the problem? Do
not even go back to the beginning. The problem is repeated below.

Grid Problem Specifications

An image can be represented as a grid of black and white cells. Two cells in an
image are part of the same object if each is black and there is a sequence of moves
from one cell to the other, where each move is either horizontal or vertical to an
adjacent black cell. For example, the diagram below represents an image that
contains two objects, one of them consisting of a single cell. Black cells are
depicted with a small black diamond.


      
  
   
 
 

Chapter XXXVII Recursion II 37.58


      
     
 

The following declarations are given:

enum CellType {Black,White};


typedef apmatrix <CellType> ImageType;

Write function AlterGrid with the following header such that all cells in the same
object as Grid[Row,Col] are set to White; otherwise Grid is unchanged.

void AlterGrid(ImageType Grid, int R, int C);

The solution to this problem is slightly altered from the orginal specifications. It
is a little tricky to create black and white squares in a grid. However, the spirit of
the problem can be simulated with text characters, using Xs and Os.

I will leave you to ponder over the solution of the problem. It is recursive. It is
certainly elegant. Is this elegance starting to make sense? You may need to do
some tracing to see what is happening. But it is now your show. You have a
program to work with, and you have the solution. This will be a good way to
finish this section by carefully digesting an excellent example of a problem that
was meant to be done recursively. The program may seem long and complex, but
actual recursive Alter function is quite short and shown at the end of the program.

// PROG3718.CPP
// Recursive Grid Problem Solution

#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#include "APMATRIX.H"

typedef apmatrix <int> Matrix;


void CreateGrid(Matrix &Grid);
void DisplayGrid(const Matrix &Grid);
void EnterData(int &Row, int &Col);
void Alter(Matrix &Grid, int R, int C);

Chapter XXXVII Recursion II 37.59


void main()
{
Matrix Grid(11,11);
int Row,Col;
CreateGrid(Grid);
DisplayGrid(Grid);
EnterData(Row,Col);
Alter(Grid,Row,Col);
DisplayGrid(Grid);
}

void CreateGrid(Matrix &Grid)


{
int R,C,Rnd;
randomize();
for (R = 1; R <= 10; R++)
for (C = 1; C <= 10; C++)
{
Rnd = random(2);
if (Rnd == 1)
Grid[R][C] = 1;
else
Grid[R][C] = 0;
}
}

void DisplayGrid(const Matrix &Grid)


{
int R,C;
cout << endl;
for (R = 1; R <= 10; R++)
{
for (C = 1; C <= 10; C++)
if (Grid[R][C])
cout << "X ";
else
cout << "O ";
cout << endl;
}
}

void EnterData(int &Row, int &Col)


{
cout << endl;

Chapter XXXVII Recursion II 37.60


cout << "Enter Row <space> Col value ===>> ";
cin >> Row >> Col;
}

void Alter(Matrix &Grid, int R, int C)


{
if ((R >= 1) && (R <= 10) && (C >= 1) && (C <= 10))
if (Grid[R][C])
{
Grid[R][C] = 0;
Alter(Grid,R-1,C);
Alter(Grid,R+1,C);
Alter(Grid,R,C-1);
Alter(Grid,R,C+1);
}
}

There are four statements that make recursive calls. Think why that is necessary
and what it does to the program’s execution. The program output with one
sample execution is shown on the next page.

PROG3718.CPP OUTPUT

X O X X O X X X X X
X X O O O O X O X X
O X X O X O O X O O
O X O X O X O O X X
O X X X X X X O O X
O X X O O O X X X X
X X O X X O X X X O
X X X X X O O X O O
O X X O O O O O O O
O O X O X X X X O O

Chapter XXXVII Recursion II 37.61


Enter Row <space> Col value ==>> 1 1

O O X X O X X X X X
O O O O O O X O X X
O O O O X O O X O O
O O O O O O O O O O
O O O O O O O O O O
O O O O O O O O O O
O O O O O O O O O O
O O O O O O O O O O
O O O O O O O O O O
O O O O X X X X O O

Original Recursion I Conclusion


This concludes the recursion concepts that were first presented in Chapter XXIII.
You will find that it is the bigger part of the entire chapter. The next sections will
move on to the true Recursion II material. Make sure that you are comfortable
with the first part of recursion before you move on. Recursion is a powerful tool
in computer science that can confuse people in the beginning. You will probably
find that reading the chapter several times, listening to the lectures, and doing the
program assignments will start more and more recursive lights burning upstairs.

37.12 The Merge Sort Case Study

It is not always the case that programmers must select between a recursive
solution or an iterative solution. There are times when it makes the most sense to
combine both methods. Keep in mind that recursion is slower, and recursion uses
more memory than iteration. This means it is wise to use iteration whenever it is
possible to do so without creating unnecessary complexity. One such example of
combining iteration with recursion is the Merge Sort.

Chapter XXXVII Recursion II 37.62


In this section a brief Merge sort case study will be presented. By now you
should know the difference between a straight forward Merge Sort presentation
and a Merge Sort case study. The case study develops the program in stages and
explains each step in the program development. Most programs are presented in
their completed format, and frequently such an approach is difficult to digest for
more complex program examples. Case studies are meant to be studied by
students to learn computer science concepts. The Merge Sort is an important
algorithm for sorting purposes and Merge Sort is also an excellent example of the
effectiveness of using recursion and iteration together.

Case Study, Step 1, A Stub Program for Merging

In the first step of our case study we need to investigate the concept of merging
and understand why merging is beneficial to a sorting algorithm. Imagine that
two sorted lists already exist, called L1 and L2. You wish to combine the two
lists and finish with a list called L3 that is sorted as well.

One approach is to place L1 after L2 in list L3, and then use a known sorting
algorithm. Such an approach works, but the selected sorting algorithm will
probably not take advantage of the fact that L1 and L2 are already sorted. It is far
more efficient to use a merging algorithm. The diagrams that follow below, and
on the next page, show the merging process.

List L1 22 34 35 39 50 72 75 90 92

List L2 17 18 29 46 61 82 85

22 34 35 39 50 72 75 90 92
17 18 29 46 61 82 85

The drawing above shows List1 in the higher position and List2 in the lower
position. The drawing below shows the completed merging process.

17 18 22 29 34 35 39 46 50 61 72 75 82 85 90 92

Chapter XXXVII Recursion II 37.63


The first program example is a “stubs only” program that shows the general
approach that will be followed by the examples that are used in this section. First,
there is function Initialize, responsible for storing data in lists being merged.
Second, there is function Merge, which performs the process that we want to
investigate. Third, there is function DisplayLists, which shows the contents of
the lists after the call to function Merge.

// PROG3719.CPP
// Merge Case Study, Step 1
// Stub program for the MergeSort case study

typedef apvector <int> IntList;

void Initialize(IntList &L1, IntList &L2)


{
}

void Merge(IntList &L1, IntList &L2, IntList &L3)


{
}

void Display(IntList &L1, IntList &L2, IntList &L3)


{
}

void main()
{
IntList List1,List2,List3;
Initialize(List1,List2);
Merge(List1,List2,List3);
Display(List1,List2,List3);
}

PROG3719.CPP OUTPUT

There is no output for this program

Case Study, Step 2, Merging Two Sorted Lists

Program PROG3719.CPP was not very satisfying. It did not even have any kind
of output. Not to worry, because the next program cures that problem. The next
example will take two sorted lists and merge them into one longer, sorted list.

Chapter XXXVII Recursion II 37.64


Keep in mind that we do not have a fully functional sort routine here. The
assumption is that two lists already exist that are sorted. The Merge function can
then create a bigger list. How you manage to start with sorted lists is a question
for a later step in the case study.

The biggest concern with step 2 is to understand the merging process. The
Initialize function creates one list of odd integers and a second list of even
integers.

The Display function displays the contents of every list. The important stage here
is to focus on function Merge. In the next program, Merge is presented as a stub.
Check the program out to see the general flow of information, look at the program
output and then move on to see the merging logic.

// PROG3720.CPP
// Merge Case Study, Step 2
// This program demonstrates merges two sorted arrays.

#include <iostream.h>
#include <conio.h>
#include "APVECTOR.H"

typedef apvector <int> IntList;


void Initialize(IntList &L1, IntList &L2);
void Merge(IntList &L1, IntList &L2, IntList &L3);
void Display(IntList &L1, IntList &L2, IntList &L3);

void main()
{
IntList List1(100),List2(100),List3(100);
Initialize(List1,List2);
Merge(List1,List2,List3);
Display(List1,List2,List3);
}

Chapter XXXVII Recursion II 37.65


void Initialize(IntList &L1, IntList &L2)
{
int K,L;
clrscr();
L = 10;
for (K = 0; K < 10; K++)
{
L1[K] = L;
L++;
L2[K] = L;
L++;
}
}

void Merge(IntList &L1, IntList &L2, IntList &L3)


{
// details shown separately
}

void Display(IntList &L1, IntList &L2, IntList &L3)


{
int K;
for (K = 0; K < 10; K++)
cout << L1[K] << " ";
cout << endl << endl;
for (K = 0; K < 10; K++)
cout << L2[K] << " ";
cout << endl << endl;
for (K = 0; K < 20; K++)
cout << L3[K] << " ";
cout << endl << endl;
}

PROG3720.CPP OUTPUT

10 12 14 16 18 20 22 24 26 28

11 13 15 17 19 21 23 25 27 29

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
28 29

Chapter XXXVII Recursion II 37.66


Function Merge will appear complex at first, but concentrate on one of the loops
at a time and the complexity may seem not quite so bad. Function Merge
requires some extra baggage because the two lists are not necessarily of equal
length, as they are in the presented example.

void Merge(IntList &L1, IntList &L2, IntList &L3)


{
int I=0, K=0, J=0;
/*1*/ while (I < 10 && J < 10)
{
if (L1[I] <= L2[J])
{
L3[K] = L1[I];
I++;
}
else
{
L3[K] = L2[J];
J++;
}
K++;
}
/*2*/ while (I < 10)
{
L3[K] = L1[I];
I++;
K++;
}
/*3*/ while (J < 10)
{
L3[K] = L2[J];
J++;
K++;
}
}

The /*1*/ while loop is the work horse of the merging process. The first element
of L1 and L2 are compared and the smaller of the two elements is placed in L3.

Chapter XXXVII Recursion II 37.67


The index of L1 or L2 (I or J) is advanced if the element is merged into L3. At
the conclusion of the /*1*/ while either L1 or L2 is completely transferred to L3.

The /*2*/ while loop merges any remaining L1 elements into L3. It is no longer
necessary to compare L1 with L2 since either L1 or L2 has been completely
transferred into L3. The /*2*/ while loop is skipped over when (I >= 10) is true,
meaning that L1 is completely merged into L3.

The /*3*/ while loop merges any remaining L2 elements into L3. The /*3*/
while loop is skipped over when (J >= 10) is true, meaning that every element of
L2 has been merged into L3.

Case Study, Step 3 Merging 2 Halves of 1 Array

The next stage in this case study may seem a little strange, but keep in mind that
we are slowly building up to an algorithm that will do more than merge. Our goal
is to write a function that will sort a set of random data. How then can merge be
used if there are not any sorted lists to merge? Stay tuned, we will develop this
process slowly and hopefully it will make sense when it is all over.

In the next program example there are a few significant changes. Function
Initialize places even integers in the first half of the array and odd integers in the
second half of the array. You will not see two separate arrays in this program.
The purpose of this program is to take two sorted halves of the same array and use
the merge process to end up with a completely sorted array.

Understanding how merging is possible within a single array is very significant in


the process of understanding the whole merge sort routine. Keep in mind that
eventually you will need to take some random, single array and sort all its data.

Several important changes have been made in function Merge to make this
“single array” concept possible. First, a locally declared list, Temp, is used to
store the merged data. At the conclusion of the merge process, the data in Temp
will be transferred back to the Lst array. Second, a midpoint in the array is found
and the single array is really behaving like two arrays for the merge process by
logically treating each half of the Lst array as a separate array. From an actual
merging point of view, the second example is the same as the first. Merging is
merging, but the new feature introduced here is the idea of treating one array as if
it consisted of two separate arrays.

Chapter XXXVII Recursion II 37.68


The data is still nicely manipulated in the Initialize function with totally
unrealistic data so that we can focus on the Merge function. Have patience, it
will be done with random data soon enough.

// PROG3721.CPP
// Merge Case Study, Step 3
// This program merges two sorted halves of one array

#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#include "APVECTOR.H"

typedef apvector <int> IntList;

void Initialize(IntList &Lst, int N);


void Merge(IntList &Lst, int N);
void Display(IntList &Lst, int N);
void main()
{
IntList List(100);
int N = 16;
Initialize(List,N);
Display(List,N);
Merge(List,N);
Display(List,N);
}

void Initialize(IntList &Lst, int N)


{
int K,L;
int Mid;
clrscr();
L = 10;
Mid = N / 2;
for (K = 0; K < Mid; K++)
{
Lst[K] = L;
L++;
Lst[K+Mid] = L;
L++;
}
}

Chapter XXXVII Recursion II 37.69


void Display(IntList &Lst, int N)
{
int K;
for (K = 0; K < N; K++)
cout << Lst[K] << " ";
cout << endl << endl;
getch();
}

void Merge(IntList &Lst, int N)


{
int I,J,K;
IntList Temp(100);
int Mid = N / 2;
I = 0;
J = Mid;
K = 0;
while (I < Mid && J < N)
{
if (Lst[I] <= Lst[J])
{
Temp[K] = Lst[I];
I++;
}
else
{
Temp[K] = Lst[J];
J++;
}
K++;
}

while (I < Mid)


{
Temp[K] = Lst[I];
I++;
K++;
}
while (J < N)
{
Temp[K] = Lst[J];
J++;
K++;
}
for (K=0; K < N; K++)

Chapter XXXVII Recursion II 37.70


Lst[K] = Temp[K];
}

PROG3721.CPP OUTPUT

10 12 14 16 18 20 22 24 11 13 15 17 19 21 23 25

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

Case Study, Step 4 Merging Parts of One Array

In Step 3 we deliberately merged the first half of the array with the second half of
the array. This process was relatively simple and allowed us to investigate how
merging is possible in a single array. In Step 4 we will continue merging in one
array, but now we will designate one particular section of an array as requiring
merging.

The array section will be identified by using the variable First as the index where
the partial array starts, and Last as the index where the partial array ends. The
variable Mid indicates the Midpoint index between the First index and the Last
index. The importance of merging only part of an array will be explained in the
next section. It is the last programming tool that needs to be achieved before we
can get really serious, and truly start doing some honest sorting.

It is ironic that computer science makes such a big deal about solving problems
(and programs) in a top-down manner, yet it is frequently the case that the
introduction of a new topic is managed easier if it is handled with a bottom-up,
from specific to general, learning approach.

// PROG3722.CPP
// Merge Case Study, Step 4
// This program demonstrates how to merge part of one
array.

#include <iostream.h>
#include <conio.h>
#include "APVECTOR.H"

Chapter XXXVII Recursion II 37.71


typedef apvector <int> IntList;

void Initialize(IntList &Lst, int N);


void Merge(IntList &Lst, int First, int Mid, int Last);
void Display(IntList &Lst, int N);

void main()
{
IntList List(100);
int N = 16;
Initialize(List,N);
Display(List,N);
int Mid = N / 2;
int First = Mid - 4;
int Last = Mid + 3;
Merge(List,First,Mid,Last);
Display(List,N);
}

void Initialize(IntList &Lst, int N)


{
int K;
int Mid = N / 2;
int L = 10;
L = 10;
clrscr();
for (K = 0; K < Mid; K++)
{
Lst[K] = L;
L++;
Lst[K+Mid] = L;
L++;
}
}

void Merge(IntList &Lst, int First, int Mid, int Last)


{
IntList Temp(100);
int I = First;
int J = Mid;
int K = First;

Chapter XXXVII Recursion II 37.72


while (I < Mid && J <= Last)
{
if (Lst[I] <= Lst[J])
{
Temp[K] = Lst[I];
I++;
}
else
{
Temp[K] = Lst[J];
J++;
}
K++;
}
while (I < Mid)
{
Temp[K] = Lst[I];
I++;
K++;
}
while (J <= Last)
{
Temp[K] = Lst[J];
J++;
K++;
}
for (K=First; K <= Last; K++)
Lst[K] = Temp[K];
}

void Display(IntList &Lst, int N)


{
int K;
for (K = 0; K < N; K++)
cout << Lst[K] << " ";
cout << endl << endl;
getch();
}

PROG3722.CPP OUTPUT

10 12 14 16 18 20 22 24 11 13 15 17 19 21 23 25

10 12 14 16 11 13 15 17 18 20 22 24 19 21 23 25

Chapter XXXVII Recursion II 37.73


Case Study, Step 5 The Complete Merge Sort

Later in this case study you will see the full-blown, ready-to-get-to-work Merge
Sort, but first you need to understand how the algorithm works. Long, long ago
you were introduced to the Bubble Sort. Understanding the logic of the Bubble
Sort was based on the Swap function. Given that you have the ability to
exchange array elements with some magical Swap function, the logic of the
Bubble sort was developed.

A similar approach will be use with the Merge Sort. We used three steps to look
at just merging array elements. Hopefully, merging makes sense at this stage.
We will now move on and assume that a merge function is available whenever it
is needed for our sort routine.

Understanding Merge Sort at the Logical Level

Let us start by actually sorting an array of eight numbers without looking at any
program code. All we want to see are pictures and the process by which the
numbers become sorted.

We start with a list of eight, unsorted, numbers, shown in figure 1, and then
proceed to manipulate these numbers by some logical fashion until the list is
sorted. If we can discover a method for eight numbers there may be a good
chance that it will work for larger arrays as well.

Figure 1

456 143 678 342 179 809 751 500

First we need to split the array into two parts and check to see if each half of the
array can be merged into one larger array. We know that sorting is possible if we
have access to two lists that are sorted already.

Figure 2

Chapter XXXVII Recursion II 37.74


456 143 678 342 179 809 751 500

Figure 2 shows how the array splits into two lovely halves. We have a merge
function available, but correct merging requires that the lists to be merged are
already sorted. All we have right now are two smaller arrays of four elements,
and each smaller array is still unsorted. Merging at this stage will only rearrange
the array in some useless, unsorted, fashion. How about splitting each one of the
smaller arrays? Perhaps that will help.

Figure 3

456 143 678 342 179 809 751 500

Well this is just terrific. Figure 3 has four smaller arrays, and surprise, each one
of the arrays is as unsorted as when we started. We do not have one big problem
now, we have four little problems. We are doing very little merging, but we are
sure splitting very well. Hang on, and just for fun humor us and split one more
time. Maybe, just maybe, something useful will happen.

Figure 4

456 143 678 342 179 809 751 500

Figure 4 probably does not create tremendous excitement. We have now


managed to split the original array so far that no more splitting can be done. This
brings up an interesting question. Is a list with one element sorted? The question
may seem peculiar, but the idea is very significant. Keep in mind that we have
the capability of merging lists, provided they are sorted. We have not managed
to do any merging, because we have not had any sorted lists to work with. A list
with one element is very small, and it is also sorted. This mean that now we can
merge, and let us do this with eight lists and merge them into four sorted lists.

Figure 5

143 456 342 678 179 809 500 751

Chapter XXXVII Recursion II 37.75


Now we are getting somewhere. Four little merges have been performed, and
each one of the merges created a small, but sorted array of two elements. We are
now seeing something really useful because the four small lists in Figure 5 can be
merged into two sorted lists of four elements.

Figure 6

143 342 456 678 179 500 751 809

Some serious celebration can start right about now. It appears that Figure 6
shows two sorted lists. We are only one merge process away from having the
whole works sorted. What you see here is custom ordered for our Merge
function. We have two lists, and they are both sorted.

Figure 7

143 179 342 456 500 678 751 809

Success in Figure 7. We performed three merge passes and the whole list is
sorted. Let us now try to summarize what was actually done in English sentences,
before we attempt to write C++ code. After we have some logical steps
sequenced in easy to understand, English sentences, we will have an easier time to
see about changing the English C++ programming source code.

Merge Sort Algorithm

As long as the lists are greater than one, continue the


following four steps:

[1] Find the midpoint of the current list


[2] Make a recursive call to sort the first half of the list
[3] Make a recursive call to sort the second half of the list

Chapter XXXVII Recursion II 37.76


[4] Merge the two sorted halves of the list

The beauty of the Merge Sort algorithm is the way that recursion works. All
unfinished business sits patiently on the stack waiting to jump into action. This
feature is perfect for our Merge Sort.

Merging two lists is useless unless the two lists are sorted. For this reason two
recursive calls are made first to sort the two halves of the list. This recursive
process continues until a single element is left in the list, just like the examples,
shown with Figure 1 through Figure 7. Merging can now proceed. It starts
slowly by merging single elements, but rapidly the list grows until the whole list
is sorted.

We can also see an excellent example of mixing recursion with iteration. The
Merge function is strictly iterative and happily merges any sorted data that
comes its way. The overall MergeSort is recursive in nature and allows
continues recursive calls until the list is totally broken up into single elements.

The exit or base case of the recursive function requires recognition that the list
cannot be broken up any further. The array that is used to store the number list
does not have any parameter that keeps track of the size of the list. Somehow, we
need to know when the list is reduced to a single element.

We can borrow some logic from the Binary Search. With the Binary Search you
use a High and a Low. At the start of the search Low = 0 and High = N-1, with
N equal to the number of elements in the array. As the search progresses, High
and Low are altered depending on the comparisons made with the search item. In
the event that the search item does not exist, High will actually become smaller
than Low. When that particular condition is true (High < Low), it is time to quit
because all possibilities have been exhausted.

We will use the same logic with the MergeSort. In this case we will use First
and Last. At the first function call to MergeSort , First = 0, and Last = N-1. As
the array gets broken down with each recursive call, the values of First and Last
are altered. Eventually, Last becomes smaller than First, and when that happens
the array has been broken down as far as possible and the recursive process has
reached its base case. At that point the merging process can now get started.

Chapter XXXVII Recursion II 37.77


Coding the Merge Sort Function

We are now ready to code the actual MergeSort function. Keep in mind that
function Merge already exists. Shortly, in the final step, an entire program will
be shown that ties all the pieces together. Right now let us focus on the
MergeSort coded with the assumption of an existing Merge function.

void MergeSort(IntList &Lst, int First, int Last)


{
int Mid;
if (First < Last) // Line 1
{
Mid = (First + Last) / 2; // Line 2
MergeSort(Lst,First,Mid); // Line 3
MergeSort(Lst,Mid+1,Last); // Line 4
Merge(Lst,First,Mid,Last); // Line 5
}
}

It is hard to believe that the short MergeSort function can actually sort anything,
and it does the sorting very efficiently. The MergeSort is recognized as one of
the superior sorts. Keep in mind that another, and longer Merge, function is part
of the overall sorting process.

In line 1 the function checks to see if the base case has been reached. Once Last
is no longer greater than First, recursion stops.

Line 2 computes the midpoint index of the current array, which is specified by
the index of First and Last. This midpoint will be used to make two recursive
calls with two smaller arrays.

Line 3 calls the MergeSort function recursively with altered parameters that use
the first half of the current list. Essentially, this splits the list in two.

Line 4 calls the MergeSort function recursively with altered parameters that use
the second half of the current list.

Chapter XXXVII Recursion II 37.78


Specifying current list rather than just saying list is intentional. The original list
is split many times. With each recursive call the current list is split into two
halves. These two halves each become the new current list at the start of the
next function call.

Line 5 takes the two halves, which were sorted by the two previous recursive
calls, and merges them iteratively with our handy Merge function.
Program PROG3723.CPP shows a complete program that demonstrates the
MergeSort function. This program will generate random numbers, display the
random numbers, and then sort them. The Display function will be called a
second time to verify that the list has been sorted.

// PROG3723.CPP
// Merge Case Study, Step 5
// This program demonstrates the complete MergeSort
program

#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#include "APVECTOR.H"

typedef apvector <int> IntList;


void Initialize(IntList &Lst, int &N);
void Merge(IntList &Lst, int First, int Mid, int Last);
void Display(IntList &Lst, int N);
void MergeSort(IntList &Lst, int First, int Last);

void main()
{
IntList List(1000);
int N;
Initialize(List,N);
Display(List,N);
MergeSort(List,0,N-1);
Display(List,N);
}

void Initialize(IntList &Lst, int &N)


{
int K;
clrscr();
cout << "Enter how many numbers will be used ===>>
";

Chapter XXXVII Recursion II 37.79


cin >> N;
for (K = 0; K < N; K++)
Lst[K] = random(900) + 100;
}

void Display(IntList &Lst, int N)


{
int K;
int L = 0;
cout << endl << endl;
for (K = 0; K < N; K++)
{
cout << Lst[K] << " ";
L++;
if (L % 12 == 0)
cout << endl;
}
getch();
}
void Merge(IntList &Lst, int First, int Mid, int Last)
{
IntList Temp(1000);
int I = First;
int J = Mid + 1;
int K = First;
while (I <= Mid && J <= Last)
{
if (Lst[I] <= Lst[J])
{
Temp[K] = Lst[I];
I++;
}
else
{
Temp[K] = Lst[J];
J++;
}
K++;
}
while (I <= Mid)
{
Temp[K] = Lst[I];
I++;
K++;
}
while (J <= Last)

Chapter XXXVII Recursion II 37.80


{
Temp[K] = Lst[J];
J++;
K++;
}
for (K=First; K <= Last; K++)
Lst[K] = Temp[K];
}

void MergeSort(IntList &Lst, int First, int Last)


{
int Mid;
if (First < Last)
{
Mid = (First + Last) / 2;
MergeSort(Lst,First,Mid);
MergeSort(Lst,Mid+1,Last);
Merge(Lst,First,Mid,Last);
}
}

PROG3723.CPP OUTPUT

Enter how many numbers will be used ===>> 100

109 103 401 129 420 295 583 276 730 954 347 499
198 728 607 137 248 833 716 787 844 963 297 484
957 855 930 829 505 644 695 639 594 748 202 465
209 704 527 541 607 410 881 337 261 480 725 247
584 680 661 102 808 342 514 447 439 623 643 351
253 824 365 400 467 316 472 148 544 985 101 468
162 166 328 977 419 464 277 277 290 324 782 900
914 762 515 578 131 217 512 535 637 327 203 731
684 897 887 138

101 102 103 109 129 131 137 138 148 162 166 198
202 203 209 217 247 248 253 261 276 277 277 290

Chapter XXXVII Recursion II 37.81


295 297 316 324 327 328 337 342 347 351 365 400
401 410 419 420 439 447 464 465 467 468 472 480
484 499 505 512 514 515 527 535 541 544 578 583
584 594 607 607 623 637 639 643 644 661 680 684
695 704 716 725 728 730 731 748 762 782 787 808
824 829 833 844 855 881 887 897 900 914 930 954
957 963 977 985

37.13 Mutual Recursion


In this section we are looking at an unusual type of recursion that is called mutual
recursion. This type of recursion occurs when a function does not call itself but
calls another function. The second function then turns around and calls the first
function. The regular recursive concepts, such as the need for a base case, still
apply, but the process is more complex.

Program PROG3724.CPP demonstrates the process. The object of the program


is to generate two arrays of random integers. The first array, List1, generates
integers in the [10..99] range. The second array, List2, generates integers in the
[100..999] range.

Function Initialize generates the random integers and stores the integers in the
two arrays. List1 will be displayed in a column on the left side, and List2 will be
displayed in a column on the right side. Function LeftColumn and function
RightColumn display the integer values in the appropriate column and call each
other with mutual recursion.
Note that function prototypes are used. Since the early introduction of functions
you have been advised to use function prototypes. There have been many
situations where function prototypes were not required. Nevertheless you have
learned that this is an established convention that you should follow. In this case
it will be required to use function prototypes. Function RightColumn is declared
after function LeftColumn. Function LeftColumn includes a call to function
RightColumn, and this call is only possible because the program uses prototypes.

// PROG3724.CPP
// This program demonstrates mutual recursion.

Chapter XXXVII Recursion II 37.82


#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#include "APVECTOR.H"

typedef apvector <int> IntList;

void Initialize(int &N, IntList &Lst1, IntList &Lst2);


void LeftColumn(int K, int N, IntList &Lst1, IntList
&Lst2);
void RightColumn(int K, int N, IntList &Lst1, IntList
&Lst2);

void main()
{
int Count;
clrscr();
IntList List1(100),List2(100);
Initialize(Count,List1,List2);
LeftColumn(0,Count,List1,List2);
getch();
}

void Initialize(int &N, IntList &Lst1, IntList &Lst2)


{
int K;
clrscr();
cout << "Enter how many numbers will be used ===>>
";
cin >> N;
cout << endl;
for (K = 0; K < N; K++)
{
Lst1[K] = random(900) + 100;
Lst2[K] = random(90) + 10;
}
}

void LeftColumn(int K, int N, IntList &Lst1, IntList


&Lst2)
{
cout << Lst1[K] << " ";

Chapter XXXVII Recursion II 37.83


RightColumn(K,N,Lst1,Lst2);
}

void RightColumn(int K, int N, IntList &Lst1, IntList


&Lst2)
{
cout << Lst2[K] << " " << endl;
if (K < N-1)
LeftColumn(K+1,N,Lst1,Lst2);
}

PROG3724.CPP OUTPUT

Enter how many numbers will be used ===>> 10

109 10
401 12
420 29
583 27
730 95
347 49
198 72
607 13
248 83
716 78

There is a conditional statement in function RightColumn to stop the mutual


recursion. You may be surprised that both functions do not have an exit, but this
is not necessary for LeftColumn. There is always going to be a value displayed
in the right column for every value displayed in the left column. For this reason
the decision to stop is made by function RightColumn.

Combining Mutual Recursion and Self Recursion

The previous example works fine to demonstrate mutual recursion, but the
purpose of the program example was somewhat contrived. You can probably
accept the situation where two arrays of numbers need to be displayed in separate
columns. But there will certainly be situations where one array has more
elements than the other array.

Chapter XXXVII Recursion II 37.84


Arrays of different sizes cannot use the short functions of the previous program
example. The larger array needs to switch from mutual recursion to self recursion
when all the numbers of the smaller array have been displayed. It will be
necessary to provide an exit in both functions.

Program PROG2525.CPP shows the added complexity necessary to handle


arrays of different sizes. In particular, notice that both functions need to consider
the base case that will stop the recursive calling.

The interesting thing in this program is that there are different exits. First, there is
an exit that stops making mutual recursive calls. At that point self recursion
continues in one of the function. The function that continues with self recursion
now will need a second exist to stop the self recursive calls.

Study the next program example carefully and make sure that you can follow the
program execution as it goes through the initial stage of mutual recursion and then
later switches to self recursion.

// PROG3725.CPP
// This program demonstrates mutual and self recursion.

#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#include "APVECTOR.H"

typedef apvector <int> IntList;

void Initialize(int &N1, int &N2, IntList &Lst1,


IntList &Lst2);
void LeftColumn(int K, int N1, int N2,
IntList &Lst1, IntList &Lst2);
void RightColumn(int K, int N1, int N2,
IntList &Lst1, IntList &Lst2);

void main()
{
int Count1,Count2;
clrscr();
IntList List1(100),List2(100);
Initialize(Count1,Count2,List1,List2);
LeftColumn(0,Count1,Count2,List1,List2);
getch();

Chapter XXXVII Recursion II 37.85


}

void Initialize(int &N1, int &N2, IntList &Lst1,


IntList &Lst2)
{
int K;
clrscr();
cout << "Enter how many numbers will be used for
List1 ==> ";
cin >> N1;
cout << endl;
for (K = 0; K < N1; K++)
Lst1[K] = random(900) + 100;
cout << "Enter how many numbers will be used for
List2 ==> ";
cin >> N2;
cout << endl;
for (K = 0; K < N2; K++)
Lst2[K] = random(90) + 10;
}

void LeftColumn(int K, int N1, int N2,


IntList &Lst1, IntList &Lst2)
{
if (K < N1)
cout << Lst1[K] << " ";
if (K < N2)
RightColumn(K,N1,N2,Lst1,Lst2);
else
if (K < N1)
{
cout << endl;
LeftColumn(K+1,N1,N2,Lst1,Lst2);
}
}

void RightColumn(int K, int N1, int N2,


IntList &Lst1, IntList &Lst2)
{
if (K < N2)
cout << Lst2[K] << " " << endl;
if (K < N1)
LeftColumn(K+1,N1,N2,Lst1,Lst2);
else

Chapter XXXVII Recursion II 37.86


if (K < N2)
{
cout << " ";
RightColumn(K+1,N1,N2,Lst1,Lst2);
}
}

PROG3725.CPP OUTPUT

Enter how many numbers will be used for List1 ==> 10

Enter how many numbers will be used for List2 ==> 5

109 34
103 49
401 19
129 72
420 60
295
583
276
730
954

Mutual Recursion Definition

Mutual recursion is the process when two functions call


each other. With two functions, called A and B, this means
that A calls B and B calls A.

At least one of the functions must have a base case to


stop the mutual recursive calls.

Mutual recursion requires the use of function prototypes.


Without prototypes the program will not compile since

Chapter XXXVII Recursion II 37.87


one of the functions is calling a function that is declared
later in the program.

Chapter XXXVII Recursion II 37.88

You might also like