Chapter 5: Recursion as a
Problem-Solving Technique
BACKTRACKING
RECURSIVE GRAMMARS
MATRIX OPERATIONS
MATHEMATICAL INDUCTION
RECURRENCE RELATIONS
CS 240
19
Recursion can be a very effective
technique for solving what would
otherwise be extremely elaborate
problems.
Sample recursive applications
include:
Backtracking algorithms.
Programming language definition.
Matrix operations.
In addition, a solid understanding of
recursion is helpful in analyzing the time
complexity of algorithms.
Recursion-related analysis techniques
include:
Mathematical induction.
Recurrence relations.
CS 240
20
Recursive Backtracking Example:
Flood Fill Algorithm
Starting with a “seed” pixel that’s inside a
polygonal region, recursively visit the four
adjacent pixels, coloring any that haven’t
been colored, and that aren’t on the
polygon’s boundary. Reaching the
boundary is the recursion’s termination
condition.
void floodfill(int x, int y)
{
if (!filled(x,y))
{
color(x,y);
floodfill(x-1,y); // Left
floodfill(x+1,y); // Right
floodfill(x,y-1); // Up
floodfill(x,y+1); // Down
}
}
Boundary
pixel
CS 240
Seed
pixel
LRUD-visited
pixel
21
Recursive Grammar
Example: A Calculator
Languages (e.g., programming
Grammar
languages) are often defined by their
grammars, sets of recursive rules
which provide syntax.
program:
expr_list:
END
expr_list END
expression ;
expression ;
expr_list
Using this grammar, the following is a
syntactically correct calculator program:
pi = 3.1416;
rad = 2.5;
ht = 10;
area = pi * rad * rad;
surfacearea = 2 * (area + pi * rad *
expression: term + expression
term – expression
term
term:
primary:
CS 240
primary / term
primary * term
primary
NUMBER
NAME
NAME = expression
- primary
( expression )
ht);
END
Recursive grammars are also prominent in
more sophisticated languages, making the
following language features possible:
• nested loops, conditionals, function calls
• cascaded operators (<<, >>, =, etc.)
• multiple cases in a switch statement
22
Recursive Matrix Operation
Example: Determinants
Many matrix operations may be
defined recursively, with combinations
of submatrix operations used to
implement the large matrix operation.
Note that a matrix’s determinant is used to
determine, among other things, whether the
matrix is invertible.
CS 240
23
Recursive Determinant
Program Example
////////////////////////////////////////////
// Class definition file: Matrix.h
//
// The matrix class has two data members: //
// a square array of integer values (all //
// between 0 and 9), and an integer indi- //
// cating the array size. Its member
//
// functions include constructors, func- //
// tions to set and access specific array //
// elements, a function to access a sub- //
// array, a recursive determinant func//
// tion, and an output operator.
//
////////////////////////////////////////////
// Member functions
int getSize() const
{ return size; }
void setElement(int i, int j,
elementType item);
elementType getElement(int i, int j)
{ return table[i][j]; }
elementType determinant();
friend ostream& operator <<
(ostream &os, const matrix &m);
protected:
// Data members
elementType table[MAX_GRID_SIZE]
[MAX_GRID_SIZE];
int size;
#ifndef MATRIX_H
#include <fstream>
using namespace std;
typedef int elementType;
const int MAX_GRID_SIZE = 7;
// Member function
matrix minor(int i, int j);
};
class matrix
{
public:
// Class constructors
matrix()
{size = 0;}
matrix(const matrix &m);
matrix(int sz);
CS 240
#define MATRIX_H
#endif
24
////////////////////////////////////////////
// Class implementation file: Matrix.cpp //
// The implementation of the copy and
//
// initializing constructors, the setEle- //
// ment, determinant, and minor member
//
// functions, and the output operator.
//
////////////////////////////////////////////
#include <stdlib.h>
#include <assert.h>
#include <fstream>
#include <iomanip>
#include <math.h>
#include "Matrix.h"
using namespace std;
// Copy constructor: Copies existing mat.//
matrix::matrix(const matrix &m)
{
size = m.size;
for (int row = 0; row < m.size; row++)
for (int col = 0; col < m.size; col++)
table[row][col] = m.table[row][col];
}
// Initializing constructor: Sets *this //
// up as a sz x sz matrix of zeros.
//
matrix::matrix(int sz)
{
size = sz;
for (int row = 0; row < sz; row++)
for (int col = 0; col < sz; col++)
table[row][col] = 0;
}
CS 240
// SetElement Member Function: Sets the //
// (i,j) element of the matrix to item. //
void matrix::setElement(int i, int j,
elementType e)
{
assert ((0<=i) && (i<size) &&
(0<=j) && (j<size));
table[i][j] = e;
}
// Determinant Member Function: Calculates //
// & returns the determinant of the matrix. //
elementType matrix::determinant()
{
elementType value = 0;
if (size == 1) return table[0][0];
for (int col = 0; col < size; col++)
{
value += (elementType)pow(-1, 0+col) *
table[0][col] *
minor(0, col).determinant();
}
return value;
}
Notice how the size×size
matrix is being evaluated
recursively by using the top
row to expand into size (size1)×(size-1) submatrices.
25
// Output Operator: Outputs matrix //
// as a grid of size rows with
//
// size columns in each row.
//
ostream& operator << (ostream &os,
const matrix &m)
{
for (int row = 0;
row < m.getSize();
row++)
{
for (int col = 0;
col < m.getSize();
col++)
os << setw(4) << m.table[row][col];
os << endl;
}
return os;
}
CS 240
// Minor Member Function: If *this is //
// an nXn matrix, then this returns
//
// the (n-1)X(n-1) matrix that is
//
// *this w/row i & column j removed. //
matrix matrix::minor(int i, int j)
{
int subrow, subcol;
assert (size > 1);
matrix submat(size-1);
subrow = 0;
for (int row = 0; row < size; row++)
{
subcol = 0;
if (row != i)
{
for (int col = 0; col < size; col++)
if (col != j)
{
submat.setElement(subrow, subcol,
table[row][col]);
subcol++;
}
subrow++;
}
}
return submat;
}
26
////////////////////////////////////////////////////
// Program file: matrixDriver.cpp
//
// This program tests the matrix class by
//
// creating a random matrix of a user-specified
//
// size, outputting it, & taking its determinant. //
////////////////////////////////////////////////////
#include <iostream>
#include <iomanip>
#include <ctime>
#include "Matrix.h"
using namespace std;
int generateRandomNumber(int lowerBound,
int upperBound);
// The main function randomly generates & outputs //
// a square matrix,, & determines its determinant.//
void main()
{
int gridSize;
cout << "SPECIFY THE MATRIX SIZE (a positive "
<< "integer less than " << MAX_GRID_SIZE+1
<< "): ";
cin >> gridSize;
while ((gridSize<1) || (gridSize>MAX_GRID_SIZE))
{
cout << "SORRY, ONLY VALUES BETWEEN 1 AND "
<< MAX_GRID_SIZE << " ARE ACCEPTED>\n ";
cout << "SPECIFY THE MATRIX SIZE (a positive "
<< "integer less than "
<< MAX_GRID_SIZE+1 << "): ";
cin >> gridSize;
}
CS 240
matrix grid(gridSize);
for (int row=0; row<gridSize; row++)
for (int col=0; col<gridSize; col++)
grid.setElement(row, col,
generateRandomNumber(0,9));
cout << endl << "MATRIX:" << endl
<< grid << endl << endl;
cout << "DETERMINANT: "
<< grid.determinant()
<< endl << endl;
}
// The generateRandomNumber function
//
// randomly generates an integer in the //
// range between the parameterized low- //
// erBound & upperBound values (inclu- //
// sive). The first time it is called, //
// it seeds the rand() random number
//
// generation function.
//
int generateRandomNumber(int lowerBound,
int upperBound)
{
static bool firstTime = true;
time_t randomNumberSeed;
if (firstTime)
{
time(&randomNumberSeed);
srand(randomNumberSeed);
firstTime = false;
}
return (lowerBound +
int((upperBound - lowerBound) *
(float(rand()) / RAND_MAX)));
}
27
CS 240
28
A mathematical “cousin” to recursion is
the concept of induction.
When you want to prove that
something is true for all integer
values, beginning at a specific value
n0, perform the following steps:
Step One: Prove The Base Case
Formally demonstrate that it’s true for
that smallest value, n0.
Step Two: Assume For Some General
Case
Assume that the it’s been proven true
for all values through k, where k is at
least
n0.
Step Three:
Prove For The Next Case
Use the assumption that it’s been
proven true for smaller cases to prove
that it’s also true for k+1.
CS 240
29
Induction Example
Theorem A: i = 1,n i = ½n(n + 1) for all n
1.
Proof (by induction):
Step One (Prove for the base case):
For n = 1, i = 1,1 i = 1 = ½(1)(1 + 1).
Step Two (Assume for some general
case):
Assume for n = k: i = 1,k i = ½k(k + 1).
Step
Three (Prove for the next case):
Prove for n = k + 1:
i = 1,k+1 i
= (k + 1) + i = 1,k i
= (k + 1) + ½k(k + 1)
(by the assumption for
case k)
= ½(2)(k + 1) + ½k(k + 1)
= ½(k + 1)(k + 2)
= ½(k + 1)((k + 1) + 1).
CS 240
30
Why Does Induction Work?
To prove that i = 1,n i = ½n(n + 1) for all n
1, we started by proving that it was true
for n = 1.
Once we accomplished that, we assumed
that it was true for some arbitrary value
k and then proved that that made it true
for the next value: k + 1.
In essence, this last proof causes the truth
of the theorem to “cascade” through all
remaining values.
Proof that if it’s true for n = k, then it’s
also true for n = k + 1
TRUE
FOR
n = 1:
1=
½(1)(2)
CS 240
Letting k =
1
TRUE
FOR
n = 2:
1+2=
½(2)(3)
Letting k =
2
TRUE
FOR
n = 3:
1+2+3 =
½(3)(4)
Letting k =
3
TRUE
FOR
n = 4:
1+2+3
+4 =
½(4)(5)
Letting k =
4
TRUE
FOR
n=5:
1+2+3
+4+5 =
½(5)(6)
And so
on...
31
Induction?
Theorem Z: For any group of n people, all n
have the same height.
Proof (by induction):
Step One (Prove for the base case):
For n = 1, the group consists of a single
person, so the entire group obviously has
the same height.
Step Two (Assume for some general case):
Assume for n = k: Any group of k people have
the same height.
Step Three (Prove for the next case):
Prove for n = k + 1:
Given a group of k + 1 people, remove one
person. The resulting group of k people
must, by the inductive hypothesis, have the
same height.
Reinsert the person that was removed and
then remove a different person. The
resulting group of k people must also have
the same height.
Thus, all k + 1 people must have the same
height!
CS 240
32
One of the bridges between recursion
and induction is the recurrence relation,
which can be used to determine the
execution time of a recursive function.
int powerOf2(const int &n)
{
if (n == 0)
return 1;
return powerOf2(n-1) + powerOf2(n-1);
}
Assuming that the execution time for
arithmetic operations, condition
checking, and returning are all the
same, let’s also assume that there is a
function T(n) such that it takes T(k) time
to execute powerOf2(k).
Examination of the code above allows
us to conclude the following two facts:
T(0) = 2
T(k) = 5 + 2T(k-1) for all k > 0
CS 240
Using mathematical induction, we
can prove that:
T(k) = 7(2k)-5 for all k 0
Step One (Prove for the base case):
For n = 0, we already know that
T(0) = 2, and 7(20)-5 also
evaluates to 2.
Step Two (Assume for some
general case):
Assume for n = k: T(k) = 7(2k)-5.
Step Three (Prove for the next
case):
We need to prove it for n = k + 1:
We know that T(k+1) = 5 +
2T(k), and we’re assuming
that T(k) = 7(2k)-5, so we can
conclude that T(k+1) = 5 +
2(7(2k)-5) = 7(2k+1)-5, which is
what we wanted.
33
Let’s try the same approach on this
alternate form of the function.
int powerOf2(const int &n)
{
if (n == 0)
return 1;
return 2*powerOf2(n-1);
}
Using the same assumptions as
before, we get the following
recurrence relation:
T(0) = 2
T(k) = 4 + T(k-1) for all k > 0
This time, however, mathematical
induction tells us that:
T(k) = 4k+2 for all k 0
CS 240
34
© Copyright 2026 Paperzz