Transcript Lecture 4

1
Lecture #8
• Recursion
• How to design an Object
Oriented program
2
Recursion!
Writing a computer
program that can
play chess or checkers!
Recursion is one of the
most difficult topics in
Computer Science…
But once you master
it, you can solve all
sorts of cool problems!
Building a SuDoKu
solver!
Even cracking
codes and ciphers!
3
Idea Behind Problem
RecursionSolving
SolveAProblem(problem)
Is the problem trivially solved Yes
No
Just return
the answer
Break the problem into two
or more simpler sub-problems
Solve each sub-problem j
bybycalling
callingsome
ourself!
other
function on the sub-problem j
SolveAProblem(sub-problem1)
SomeOtherFunction(sub-problem1)
...
SolveAProblem(sub-problemn)
SomeOtherFunction(sub-problemn)
Collect all the solution(s)
to the sub-problems
Use the sub-solutions to construct
a solution to the complete problem
Return the
solution
4
“The Lazy Person’s Sort”
Let’s design a new sorting algorithm,
called the “lazy person’s sort”…
The input to this sort
are a bunch of index
cards with #s.
622
17395
14
Lazy Person’s Sort:
Split the cards into two roughly-equal piles
Hand one pile to nerdy student A and ask them to sort it
Hand the other pile to nerdy student B and ask them to sort it
Take the two sorted piles and merge them into a single sorted pile
6
17
22
3
14
95
5
“The Lazy Person’s
Sort”
Pretty good! All I had
to do was merge two
Wellof
that
worked
so
piles
sorted
cards!
well, I think I’ll have
(My nerdy
students
them
sort the
other
did all
real work!)
sixthe
hundred!
99322
2774
61492
17
6
17
Lazy Person’s Sort:
22
3
14
95
Split the cards into two roughly-equal piles
Hand one pile to nerdy student A and ask them to sort it
Hand the other pile to nerdy student
Bright.
and
ask
them
to sort it
Yeah
Hey,
I think
I have
anyou’re
idea.
That sucks. Sorting 3
Take
the two sorted piles and merge
them
into
awhen
single
kind
of be
cute
We
can
lazy
too, sorted pile
<blush>Thanks</blush>
cards was OK… but
angry!
let’syou’re
change
Carey’s
300?
I don’t
know
But
what
can we
do?
algorithm just a bit.
where to start!
6
“The
Lazy
Person’s
Sort”
I think I see it…
The 99322
algorithm isn’t complete…
2774 when a person ends
What happens
61492
up with 17
just a single notecard.
Excellent,
Very clever,
Mr. students.
Smallberg.
But your approach has one
And do you have a solution?
flaw, can you see it?
The algorithm breaks down. How
can you split a single card into two
equal piles?!!?
Well, it worked for Carey.
Split the cards into two roughly-equal piles
use
Hand one pile to nerdy student Why
A andcan’t
ask we
them
toexactly
sort Person’s
it
say
“do
the
Lazy
Sort”
the same
process
hethe
did
say them
“do
Sort”
Hand the other pile to nerdy
B and
ask
to Lazy
sort Person’s
it
hot student
Then
can blow
this
our
piles?
Take the two sorted piles and mergewith
them
into
awe
single
sorted
pile
joint and go play
Oh
yeah…
One
more
Isn’t
some kind
of all
You’rethis
a genius!
So now
And theCorrectamundo.
studentsStarCraft!
we give
thing.
Pyramid
scheme?
I think
so.
If to
youdojust
each
person
has
is each half of
our piles to
it ainbit…
splitrewrite
their pile
two,
And
nosame
one person
has
can do
the
thing too!
work?
hand Will
eachitone
to someone
to do any complex
else and then finally
sorting!
merge the results!
Lazy Person’s Sort:
7
“The Lazy Person’s Sort”
99322
2774
61492
17
Correct!
Amazing, huh? By having
an algorithm use itself
over and over, you can
solve big problems!
If you’re
handed
just one card, then just give it right back.
Lazy
Person’s
Sort:
Split the cards into two roughly-equal piles
Hand one pile to studly
nerdy student A and say
ask “do
them
toLazy
sort Person’s
it
the
Sort”
Ah, I see. When a person at the
say them
“do the
Sort”
Hand the other pile to nerdy
to Lazy
sort Person’s
it
hot student B and ask
bottom of the pyramid gets a single
Take the two sorted piles and merge them into a single sorted pile
card, they can’t split it in half…
Besides it’s just one card, so it’s
Oh yeah. And one more
technically already sorted...
thing.
So they just hand it back to the guy
above them and let them merge it.
8
“The Lazy Person’s Sort”
void MergeSort(an array)
If you’re willing to assume that
{
void OtherSort(an
MergeSort actually
works on a fullarray)
if (array’s size == 1)
array{ of data…
return;
// array has just
item,
allmerge
done! sort!
Lazy
Person’s Sort:
//1just
call
If you’re handed
card,);then
give it
right
back.
st half
array
);
OtherSort
MergeSort(
first just
half one
of array
//just
process
the
1MergeSort(
of the
array
Split the cards
intohalf
two of
roughly-equal
piles
} 2nd half of the array
MergeSort(
second
array); // process
the
OtherSort
Then A
you
should
be the
willing
to Person’s
have
Hand one pile to person
and
say “do
Lazy
Sort”
Merge(the
two array
halves);
merge
the
two sorted
faith
that it’ll
on
halfthe
an array
Hand the other
pile
to
person
B work
and//say
“do
Lazy
Person’shalves
Sort”
//
nowthe
thetwo
complete
is sorted
of
data them
too! into a single sorted pile
Take
sortedarray
piles and
merge
}
The Lazy Person’s Sort (also known as Merge Sort) is a
perfect
example
a recursive
algorithm!
When you
write aofrecursive
function…
Every time our MergeSort function is called, it breaks up its input
job is toparts
figure
out
howitself
the function
itself
intoYour
two smaller
and
calls
to solve can
eachuse
sub-part.
(on a subset of the problem) to get the complete problem solved.
It’s hard to believe it works!
but code
wouldtoyou
agree
it definitely
worksyou
if we
When you Ok
add–the
make
a function
call itself,
need to
algorithm
have faith that thatchange
call willthe
work
properlylike
(onthis?
the subset of data).
If we can rely upon OtherSort to somehow properly sort each sub-array,
It takes
somethat
timeour
to udpated
learn to MergeSort
think in thiswill
way,
but Correct?
once you
we agree
work.
“get it,” you’ll be a programming Ninja!
Ok, well let me show you the way OtherSort works 
9
The Two Rules of Recursion
RULE ONE:
Every recursive function must have a “stopping condition!”
The Stopping Condition (aka Base Case):
Your recursive function must be able to solve the
simplest, most basic problem without using recursion.
Remember: A recursive function calls itself.
Therefore, every recursive function must have some
mechanism to allow it to stop calling itself.
10
The Stopping Condition
void eatCandy(int layer)
{
if (layer == 0)
{
cout << “Eat center!”;
return;
}
cout<<“Lick layer “<<layer;
eatCandy(layer-1);
}
main()
{
eatCandy(3);
}
Here’s a simple recursive
function that shows how to
eat a tootsie-roll pop.
Can you identify the stopping
condition in this function?
What if we didn’t have this
stopping condition/base case?
Right! Our function would
never stop running.
(We’d just keep licking forever)
11
The Two Rules of Recursion
RULE TWO:
Every recursive function must have a “simplifying step”.
Simplifying Step:
Every time a recursive function calls itself, it must pass
in a smaller sub-problem that ensures the algorithm will
eventually reach its stopping condition.
Remember: A recursive function must eventually reach its
stopping condition or it’ll run forever.
12
Simplifying Code
Can you identify the simplifying
code in our print function?
void eatCandy(int layer)
{
if (layer == 0)
{
cout << “Eat center!”;
return;
}
What if we didn’t have
simplifying code?
cout<<“Lick layer “<<layer;
eatCandy(layer-1);
}
main()
{
eatCandy(3);
}
Our function would never get
closer to our stopping condition
and never stop running.
Most recursive functions simplify their
inputs in one of two ways:
1. Each recursive call divides its input
problem in half (like MergeSort)
2. Each recursive call operates on an
input that’s one smaller than the last
13
(Rule 2.5 of Recursion)
Recursive functions should never use
global, static or member variables.
They should only use local variables and
parameters!
(So be forewarned… If your recursive functions use
globals/statics/members on a test/HW, you’ll get a ZERO!)
14
void eatCandy(int layer)
{
if (layer == 0)
{
Hmm..
So where
layer
0
does our function
return to?
Tracing Through our
Function
cout << “Eat center!”;
return;
}
cout<<“Lick layer “<<layer;
void eatCandy(int layer)
{ eatCandy(layer-1);
} if (layer == 0)
layer
1
{
Output:
It’s very difficult to trace through a
}
Lick layer 2
Correct!
cout<<“Lick layer
“<<layer;
Ok, where
does
our
function that calls itself…
It continues
Lick layer 1
function
return to?running just
eatCandy(layer-1);
So,
let’s
use
a
little
trick
and pretend
after this line!
}
center!
like this call is actuallyEat
calling
a
void eatCandy(int layer)
layer 2 different function
0
{
cout << “Eat center!”;
return;
(one that just happens to have the
Correct!
main()
same
name ).
cout << “Eat center!”;
It continues running just {
return;
after this line!
if (layer == 0)
{
}
int layers = 2;
2
cout<<“Lick layer “<<layer;
1
eatCandy(layer-1);
}
eatCandy(layers);
}
Tracing Through Recursion (on Paper)
15
You’re taking a CS exam and see this:
How do you solve it quickly?
5. What does this print?
int mystery(int a)
{
if (a == 0)
return a+1;
cout << a;
a=3
if (a % 2 == 0)
a = mystery(a/3);
else
a = mystery(a-1); a = mystery(2); *
return a+5;
}
int main()
{
cout << mystery(3);
}
Output: 3
Steps:
1. Put a blank sheet of
paper next to the func.
2. Trace the func with
your finger.
A. When you hit/update a
var, write its value down.
B. Write all output on
your original sheet.
C. When you call a func:
i. Write its params down
ii. Write a * to mark where
to continue tracing later
iii. Fold the sheet in half
and continue tracing
Tracing Through Recursion (on Paper)
16
You’re taking a CS exam and see this:
How do you solve it quickly?
5. What does this print?
int mystery(int a)
{
if (a == 0)
return a+1;
cout << a;
aa==32
if (a % 2 == 0)
a = mystery(a/3); a = mystery(0); *
else
a = mystery(a-1); a = mystery(2); *
return a+5;
}
int main()
{
cout << mystery(3);
}
Output: 3 2
Steps:
1. Put a blank sheet of
paper next to the func.
2. Trace the func with
your finger.
A. When you hit/update a
var, write its value down.
B. Write all output on
your original sheet.
C. When you call a func:
i. Write its params down
ii. Write a * to mark where
to continue tracing later
iii. Fold the sheet in half
and continue tracing
Returning
a
Tracing Through
Recursion
(on Paper)
17
value of 1
You’re taking a CS exam and see this:
How do you solve it quickly?
5. What does this print?
int mystery(int a)
{
if (a == 0)
return a+1;
cout << a;
Putreturn
a blankfrom
sheet
D.1.To
a of
function:
aa=a=3=2 0
if (a % 2 == 0)
a = mystery(a/3); a = mystery(0); *
else
a = mystery(a-1); a = mystery(2);
**
return a+5;
}
int main()
{
cout << mystery(3);
}
Output: 3 2
Steps:
paper next to the func.
i. Determine what value is
2. Trace
func with
being the
returned
(if any)
your finger.
ii. Unfold your paper once.
A. When you hit/update a
iii. Find the * that points to
var, write its value down.
the line where you were
B. (you’ll
Writecontinue
all output
on here)
from
your original sheet.
iv. Erase the *
C. When you call a func:
i. Write its params down
ii. Write a * to mark where
to continue tracing later
iii. Fold the sheet in half
and continue tracing
Returning
Returning
aa
Tracing Through
Recursion
(on Paper)
18
valueof
of16
value
You’re taking a CS exam and see this:
How do you solve it quickly?
5. What does this print?
int mystery(int a)
{
if (a == 0)
return a+1;
cout << a;
}
Steps:
D. To return from a function:
aa==32
i. Determine what value is
being returned (if any)
ii. Unfold your paper once.
iii. Find the * that points to
the line where you were
(you’ll continue from here)
if (a % 2 == 0)
1
a = mystery(a/3); a = mystery(0); * iv. Erase the *
else
a = mystery(a-1); a = mystery(2); * v. Write the returned value
above your function
return a+5;
vi. Continue tracing normally.
int main()
{
cout << mystery(3);
}
Output: 3 2
Returning
a
Tracing Through
Recursion
(on Paper)
19
value
6
value of
of 11
You’re taking a CS exam and see this:
How do you solve it quickly?
5. What does this print?
int mystery(int a)
{
if (a == 0)
return a+1;
cout << a;
}
Steps:
D. To return from a function:
a=3
i. Determine what value is
being returned (if any)
ii. Unfold your paper once.
iii. Find the * that points to
the line where you were
(you’ll continue from here)
if (a % 2 == 0)
a = mystery(a/3);
iv. Erase the *
else
6
a = mystery(a-1); a = mystery(2); * v. Write the returned value
above your function
return a+5;
vi. Continue tracing normally.
int main()
{
cout << mystery(3);
}
Output: 3 2 11
20
Recursion Tracing Exercise
Use the paper tracing technique to determine what
the following program prints:
int mystery(int a)
{
cout << a;
if (a == 0)
return 1;
int b = mystery(a/2);
cout << b;
return b + 1;
}
int main()
{
cout << mystery(3);
}
21
Writing (Your Own) Recursive Functions: 6 Steps
What if we want to write our own recursive
function? How do we do it?
Step #1: Write the function header
Step #2: Define your magic function
Step #3: Add your base case code
Step #4: Solve the problem using
the magic function
Step #5: Remove the magic
Step #6: Validate your function
Let’s use these steps
to write a recursive
function to calculate
factorials.
Recall, the definition of
fact(N) is:
1
for N = 0
N * fact (N-1) for N > 0
22
Example #1: Factorial
23
Step #1: Write the function header
Figure out what argument(s) your function will
take and what it needs to return (if anything).
First, a factorial function takes in an
integer as a parameter, e.g., factorial(6).
Second, the factorial computes (and
should return) an integer result. Let’s
add a return type of int.
int fact(int n)
{
And here’s how we’d call our factorial
function to solve a problem of size n…
}
So far, so good. Let’s go on to step #2.
int main()
{
int n = 6, result;
result = fact( n );
}
24
Step #2: Define your magic function
Pretend that you are given a magic function that can compute a factorial.
It’s already been written for you and is guaranteed to work!
For example, perhaps
It takes magicfact
the same parameters
as your
looks
factorial like
function
and returns the
this?!?!
same type of result/value.
int magicfact(int x)
{
There’s
only one catch! You are
f = 1;
forbiddenint
from
passing in a value of n
> 1) function.
towhile
this (x
magic
{
f *= use
x; it to compute n!
So you can’t
x--;
} use it to solve smaller
But you can
return f;
problems, like (n-1)! or (n/2)!, etc.
}
// provided
for your use!
int
fact(int n)
{int magicfact(int x) { … }
int fact(int n)
{ // don’t worry about how your
// function will actually work!
// we’ll figure that out later!!
}
}
int main()
{
int n = 6, result;
// use magicfact to solve subproblems
Show how you could use this magic
function to compute (n-1)!.
result
result =
= magicfact(
magicfact( n-1
n ); );
}
25
Step #3: Add your base case Code
Determine your base case(s) and write the
code to handle them without recursion!
Our goal in this step is to identify the
simplest possible input(s) to our function…
And then have our function process
those input(s) without calling itself
(i.e., just like a normal function would).
Ok, so what is the simplest factorial
we might be asked to compute?
// provided
for your use!
int
fact(int n)
{int magicfact(int x) { … }
int fact(int n)
{ // don’t worry about how your
//
if function
(n == 0) will actually work!
// we’ll
figure
later!!
return
1; //that
baseout
case
}
// Always consider all possible
// base cases and add checks
// for them before proceeding!
Well, the user could pass 0 into our function.
}
0!, by definition, is equal to 1.
int main()
Let’s add a check for this and handle it
{
without using any recursion.
int n = 6, result;
In this example, this is the only base
condition, but some problems may require
2 or 3 different checks.
// use magicfact to solve subproblems
result = magicfact( n-1 );
}
26
Step #4: Solve the problem using the magic function
Now try to figure out how to use the magic function in your
new function to help you solve
the problem.
But
computing
the factorial of
It’s trivial to compute N, since
is not so earow.
Unfortunately,
youits
can’t
use the magic // N-1
we already know
value!
provided
for your use!
int fact(int n)
function to do all the work for you…
{int magicfact(int x) { … }
This is definitely a smaller
can’t no
solve
problems
of size n)
So (it
there’s
need
to use our
n) for our magic
int fact(int
problem,
perfect
//
don’t
worry
about how your
magic function to help.
{
So let’s try to break our problem into
function
to solve
for us!work!
//
function
will
actually
if (n == 0)
two (or more) simpler sub-problems and
//
we’ll
figure
later!!
return
1; //that
baseout
case
Hopefully,
we
can
agree
use our magic function to solve those.
that if this magic
Well,
by definition,
N! = N it’s
* (N-1)!
function
does what
So it’s already split into two parts for us, &
supposed to…
each part is simpler than the original problem.
Let’s
figure
outnew
a wayfunction
to solve each of
Then
our
these sub-problems.
will work correctly!
Cool! Now we can combine the results
of our sub-problems to get the
overall result!
}
}
int
int part1
result==n;
magicfact( n );
int part2 =
return result;
return part1 * part2;
int main()
{
int n = 6, result;
// use magicfact to solve subproblems
result = magicfact( n-1 );
}
27
int magicfact(int x)
Step #5: Remove the magic
{
return
fact(x);
OK, so let’s see what this magic function really looks
like!
}
Wait a second! Our magicfact
But if our function calls itself, what
function basically just calls fact!
stops it from running on forever?
That means that fact is really
Right! Our base case code ensures
just calling itself!
that our function eventually stops
calling itself!
The magicfact function hid this from
us, but that’s what’s really happening!
OK, well in that case, let’s replace
our call(s) to the magic function with
calls directly to our own function.
Will that work? Yup!
Woohoo! We’ve just
created our first recursive
function from scratch!
// provided
for your use!
int
fact(int n)
{int magicfact(int x) { … }
int fact(int n)
{ // don’t worry about how your
//
if function
(n == 0) will actually work!
// we’ll
figure
later!!
return
1; //that
baseout
case
}
}
int part1 = n;
int part2 = magicfact( n-1 );
return part1 * part2;
int main()
{
int n = 6, result;
// use magicfact to solve subproblems
result = magicfact( n-1 );
}
28
Step #6: Validating our Function
You SHOULD do this step EVERY time your write a recursive function!
Start by testing your function with the simplest possible input.
Next test your function with incrementally more complex inputs.
(You can usually stop once you’ve validated at least one recursive call)
0
int fact(int n)
{
0 == 0
if (n == 0)
return 1
1;
return
}
n * fact(n-1);
Excellent! We’ve tested all of the base
case(s) as well as validated a single
level of recursion…
We can be pretty certain our function
works now…
Good. This result is
01
Good. 0
This
resulttois1.
correct.
! is equal
int fact(int n)
int main()
correct
too.
1
!
is
equal
to
{
{
1 ==
0
== 00
1
*
0!,
aka
1
*
1,
aka
1.
if (n == 0)
cout << fact( 0 );
return 1;
cout << fact( 1 );
}
return 1n * fact(n-1);
0
}
29
int fact(int n)
Factorial Trace-through
{
int fact(int n)
{
1 == 0
if (n == 0)
return (1);
}
return(n * fact(n-1));
}
0
n
2
1
return(n * fact(n-1));
2
3
int fact(int n)
{
3 == 0
if (n == 0)
return (1);
}
1
0
return(n * fact(n-1));
1
int fact(int n)
{
2 == 0
if (n == 0)
return (1);
}
n
0 == 0
if (n == 0)
return (1);
1
n
n
2
return(n * fact(n-1));
6
result
3
6
int main()
{
int result;
result = fact(3);
cout << result;
}
30
Example #2: Recursion on an Array
For our next example, let’s learn how to use recursion
to get the sum of all the items in an array.
arr 10 20 70 14 39
0
n-1
31
You could also have written:
int *arr
It’s the same thing!
Step #1: Write the function header
Figure out what argument(s) your
function will take and what it needs
to return (if anything).
To sum up all of the items in an array, we
need a pointer to the array and its size.
int sumArr(int arr[ ], int n)
{
Our function will return the total sum
of items in the array, so we can make
the return type an int.
}
And here’s how we’d call our
array-summer function to
solve a problem of size n…
int main()
{
const int n = 5;
int arr[n] = { 10, 100, 42, 72, 16}, s;
s = sumArr( arr , n);
So far, so good.
Let’s go on to step #2.
}
// whole array
32
Step #2: Define your magic function
Pretend that you are given a
// provided for your use!
Since
we
used
n/2magicsumArr(int
to specify the #arr[],
of items
magic function that sums up the values int
int x) { … }
in the first half of the array.
in an array and returns the result…
int sumArr(int arr[ ], int n)
n – n/2 items in the
{
There’s only one catch! You areThere are
second half of the array.
forbidden from passing in an array
with n elements to this function.
If you
recall
from
then-n/2
pointer
e.g., if n=5,
then
n/2 ==
2 and
== lecture,
3
this is called “pointer arithmetic.”
So you can’t use it to sum up an
This means “give me a pointer to the
entire array (one with all n items)…
item
in the array that is 1 element
}
from the array’s start,” e.g., &arr[1]
But you can use it to sum up smaller int main()
arrays (e.g., with n-1 elements)!
{
Finally
show
to
use
the
Show
how
tohow
use
the
magic
Now
show
how
to
use
the
magic
Now
show
how
to
use
the
magic
magic
function
to
sum
thehalf
last
function
sum
the
first
n-1of
function
to
sum
the
last
n-1
function
to to
sum
the
first
half the
ofofthe
items
thearray.
array.
array.
}
const int n = 5;
int arr[n] = { 10, 100, 42, 72, 16}, s;
s = magicsumArr( arr, n-1 ); // first n-1
s = magicsumArr( arr+1,
arr , n n-1
); ); // last n-1
s = magicsumArr( arr, n/2 ); // sums 1st half
s = magicsumArr( arr+n/2, n – n/2 ); // 2nd
33
Step #3: Add your base case Code
Determine your base case(s) and
write the code to handle them
// provided for your use!
int magicsumArr(int arr[], int x) {
without recursion!
{
Well, someone could pass in a totally
empty array of size n = 0. What
should we do in that case?
if (n == 0) return 0;
if (n == 1) return arr[0];
}
int main()
{
Do we have any other base cases?
For example, what if the user passes
in an array with just one element?
Let’s see what that would look like…
Good. Let’s keep both of those.
}
int sumArr(int arr[ ], int n)
Ok, so what is the smallest array that
might be passed into our function?
Well, what’s the sum of an empty
array? Obviously it’s zero. Let’s add
the code to deal with this case.
…
}
const int n = 5;
int arr[n] = { 10, 100, 42, 72, 16}, s;
s = magicsumArr( arr, n-1 ); // first n-1
s = magicsumArr( arr+1, n-1 ); // last n-1
s = magicsumArr( arr, n/2 ); // sums 1st half
s = magicsumArr( arr+n/2, n – n/2 ); // 2nd
34
Step #4: Solve the problem using the magic function
Now try to figure out how to use the
magic function in your new function to
help you solve the problem.
// provided for your use!
int magicsumArr(int arr[], int x) {
…
}
int sumArr(int arr[ ], int n)
Unfortunately, you can’t use the magic
function to do all the work for you…
(it can’t solve problems of size n)
{
if (n == 0) return 0;
if (n == 1) return arr[0];
int s = magicsumArr( arr, n );
So let’s try to break our problem into two
(or more) simpler sub-problems and use our
magic function to solve those.
return s;
}
int main()
{
}
const int n = 5;
int arr[n] = { 10, 100, 42, 72, 16}, s;
s = magicsumArr( arr, n-1 ); // first n-1
s = magicsumArr( arr+1, n-1 ); // last n-1
s = magicsumArr( arr, n/2 ); // sums 1st half
s = magicsumArr( arr+n/2, n – n/2 ); // 2nd
35
Step #4: Solve the problem using the magic function
Now try to figure out how to use the
magic function in your new function to
help you solve the problem.
Unfortunately, you can’t use the magic
function to do all the work for you…
Hopefully,
weproblems
can agree
(it can’t solve
of size n)
// provided for your use!
int magicsumArr(int arr[], int x) {
{
if (n == 0) return 0;
if (n == 1) return arr[0];
int front =
int total = front + arr[n-1];
}
return total;
int main()
{
to process the first n-1 elements of
the array, ignoring the last element.
Once it gets the result from the magic
function, it combines it with the last
element in the array.
It then returns the full result.
}
int sumArr(int arr[ ], int n)
that if the magic
Sofunction
let’s try todoes
break what
our problem
it’s into two
(or more) simpler sub-problems and use our
supposed
magic
function to…
to solve those.
Strategy
Front
to back
Then
our #1:
new
function
work
correctly!
Your will
function
uses
the magic function
…
}
const int n = 5;
int arr[n] = { 10, 100, 42, 72, 16}, s;
s = magicsumArr( arr, n-1 ); // first n-1
s = magicsumArr( arr+1, n-1 ); // last n-1
s = magicsumArr( arr, n/2 ); // sums 1st half
s = magicsumArr( arr+n/2, n – n/2 ); // 2nd
36
Step #4: Solve the problem using the magic function
Now try to figure out how to use the
magic function in your new function to
help you solve the problem.
Unfortunately, you can’t use the magic
function to do all the work for you…
Hopefully,
weproblems
can agree
(it can’t solve
of size n)
// provided for your use!
int magicsumArr(int arr[], int x) {
{
if (n == 0) return 0;
if (n == 1) return arr[0];
int rear =
int total = arr[0] + rear;
}
return total;
int main()
{
to process the last n-1 elements of the
array, ignoring the first element.
Once it gets the result from the magic
function, it combines it with the first
element in the array.
It then returns the full result.
}
int sumArr(int arr[ ], int n)
that if the magic
Sofunction
let’s try todoes
break what
our problem
it’s into two
(or more) simpler sub-problems and use our
supposed
magic
function to…
to solve those.
Then
our#2:
new
function
Strategy
Back
to front
work
correctly!
Your will
function
uses
the magic function
…
}
const int n = 5;
int arr[n] = { 10, 100, 42, 72, 16}, s;
s = magicsumArr( arr, n-1 ); // first n-1
s = magicsumArr( arr+1, n-1 ); // last n-1
s = magicsumArr( arr, n/2 ); // sums 1st half
s = magicsumArr( arr+n/2, n – n/2 ); // 2nd
37
Step #4: Solve the problem using the magic function
Now try to figure out how to use the
magic function in your new function to
help you solve the problem.
Unfortunately, you can’t use the magic
function to do all the work for you…
Hopefully,
weproblems
can agree
(it can’t solve
of size n)
// provided for your use!
int magicsumArr(int arr[], int x) {
work
correctly!
Your will
function
uses
the magic function
to process the first half of the array.
}
int sumArr(int arr[ ], int n)
{
that if the magic
Sofunction
let’s try todoes
break what
our problem
it’s into two
(or more) simpler sub-problems and use our
supposed
magic
function to…
to solve those.
Then our
function
Strategy
#3:new
Divide
and conquer
…
if (n == 0) return 0;
if (n == 1) return arr[0];
int first =
int last =
}
return first + last;
int main()
{
Your function uses the magic function
to process the last half of the array.
Once it gets both results, it combines
them and returns the full result.
}
const int n = 5;
int arr[n] = { 10, 100, 42, 72, 16}, s;
s = magicsumArr( arr, n-1 ); // first n-1
s = magicsumArr( arr+1, n-1 ); // last n-1
s = magicsumArr( arr, n/2 ); // sums 1st half
s = magicsumArr( arr+n/2, n – n/2 ); // 2nd
38
int magicsumArr(int arr[], int x)
{
Step #5: Remove the magic
return sumArr(arr,x);
But} if our function calls itself, what
stops
forever?
OK, it
sofrom
let’s running
see whatonthis
magic
// provided for your use!
int magicsumArr(int arr[], int x) {
function really looks like!
Right! Our base case code ensures
Waitour
a second!
magicsumArr
that
functionOur
eventually
stops
function
just itself!
calls sumArr!
calling
{
The magic function hid this from us,
but that’s what’s really happening!
}
if (n == 0) return 0;
if (n == 1) return arr[0];
int first = magic sumArr( arr, n/2 );
int scnd = magic sumArr( arr+n/2,
n – n/2 );
return first + scnd;
int main()
{
Will that work? Yup!
Woohoo! We’ve just created our
second recursive function!
}
int sumArr(int arr[ ], int n)
This means that sumArr is really
just calling itself!
OK, well in that case, let’s replace
our calls to the magic function with
calls directly to our own function.
…
}
const int n = 5;
int arr[n] = { 10, 100, 42, 72, 16}, s;
s = magicsumArr( arr, n-1 ); // first n-1
s = magicsumArr( arr+1, n-1 ); // last n-1
s = magicsumArr( arr, n/2 ); // sums 1st half
s = magicsumArr( arr+n/2, n – n/2 ); // 2nd
39
Step #6: Validating our Function
You SHOULD do this step EVERY time your write a recursive function!
int sumArr(int arr[ ], int n)
{
}
Start
by testing
your function with the simplest possible input.
if (n ==
0) return
0; 20
10
if (n == 1) return arr[0];
Next test your function with incrementally more complex inputs.
int first = sumArr( arr , n/2 );
(You can usually stop once you’ve validated at least one recursive call)
int secnd = sumArr(arr+n/2, n-n/2);
Excellent!
all& of
the base case(s) as well as validated a
return
first +We’ve
secnd;tested
// combine
return
single level of recursion…
We can be pretty certain our function works now…
int sumArr(int arr[ ], int n)
{
}
if (n == 0) return 0;
if (n == 1) return arr[0];
10
1
int first = sumArr( arr
20, n/2 ); 1
int secnd = sumArr(arr+n/2, n-n/2);
30
return first + secnd; // combine & return
int main()
10 20
{
int arr[1] = { 10, 20 };
0
cout << sumArr( arr, 0 );
10 20 2
cout << sumArr( arr, 2 );
}
40
Array-summer Trace-through
int sumArr(int arr[], int n)
{
int sumArr(int arr[], int n)
{
}
if (n == 0) return 0; 10
if (n == 1) return arr[0];
20
1
int first = sumArr( arr,42
n/2 );1
int secnd = sumArr( arr+n/2, n-n/2);
return first62
+ secnd;
int sumArr(int arr[], int n)
{
}
if (n == 0) return 0;
if (n == 1) return arr[0];
1
10
int first = sumArr( 20
arr, 42
n/2 ); 2
int secnd = sumArr( arr+n/2, n-n/2);
return first72
+ secnd;
}
20
if (n == 0) return 0; 42
if (n == 1) return arr[0];
int first = sumArr( arr, n/2 );
int secnd = sumArr( arr+n/2, n-n/2);
return first + secnd;
int main()
{
const int n = 3;
int nums[n] = { 10, 20, 42 };
nums 10 20 42
3
cout << sumArr( nums , n );
}
41
Your Turn: Recursion Challenge
Write a recursive function called printArr that prints
out an array from top to bottom.
Step #1: Write the function header
Step #2: Define your magic function
Step #3: Add your base case code
Step #4: Solve the problem using
the magic function
Step #5: Remove the magic
Step #6: Validate your function
42
Recursion Challenge
Step #1: Write the function header
Write a recursive
function called
printArr that
prints out an array
from top to
bottom.
Step #2: Define your magic function
Step #3: Add your base case code
Step #4: Solve the problem using
your magic function
Step #5: Remove the magic
Step #6: Validate your function
// just assume it works (like magic)!
void magicprintArr(int arr[], int s) {…}
int main()
void printArr(int arr[], int size) First, we’ll print
out the zeroth
{
{
const
int size
= 5;
element in the
array
ourself.
if (size == 0)
int arr[size] = {7, 9, 6, 2, 4};
return;
cout << arr[0];
printArr(arr+1,size-1);
magicprintArr(arr+1,size-1);
}
Then we’ll ask our magic function to
//
printArr(arr,0);
show how to use your magic func
print the
remaining n-1 elements of
//
printArr(arr,1);
to print the last n-1 elems
the array for us.
magicprintArr(arr+1,size-1);
}
43
Recursion Challenge #2
Update your recursive function so it prints out the
items from bottom-to-top.
HINT:
You can swap just two lines in your previous function!
void printArr(int arr[], int size)
{
if (size == 0)
return;
First we’ll use recursion to print out
the last n-1 elements of the array in
reverse order.
cout << arr[0];
printArr(arr+1,size-1);
}
Then, we’ll print out the first
element in the array afterward.
44
Working
Through
Recursion
if (size == 0) // an empty array
arr 2040
Now it looks like arr has
just one element and
starts at 2040!
void reversePrint(string arr[ ], int size)
{
Cool! It’s if our array
size
starts at location 2020
and only has 2 elements!
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
return;
else
{
}
names
arr
[0]
arr
[1]
[0]
arr
[2]
[0]
[1]
void
reversePrint(string arr[ ], int size)
}
{
if (size == 0) // an empty array
return;
else
{
2020 + 1 1
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
}
}
Nan
2040
size
}
if (size == 0) // an empty array
return;
else
{
2000 + 1
2
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
Phyllis
2020
arr 2020
void
reversePrint(string arr[ ], int size)
}
{
Leslie
2000
main()
arr
{
string names[3];
size
...
2000 3
reversePrint(names,3);
}
Working
Through
Recursion
if (size == 0) // an empty array
arr 2040
45
void reversePrint(string arr[ ], int size)
{
void reversePrint(string arr[ ], int size)
{
arr 2060
return;
if (size == 0) // an empty array
size 1
else return;
size
{
else
reversePrint(arr
+ 1, size – 1 );
{
We’ve hit our base case!
cout
<< arr[0] << “\n”;
reversePrint(arr
+ 1, size – 1 );
}
cout << arr[0] arr[
<< “\n”;
void
reversePrint(string
], int size)
}
We’ve
got an empty array (of
}
{
}
size 0) so we just return!
if (size == 0) // an empty array
arr 2020
return;
size 2
else
{
}
if (size == 0) // an empty array
return;
else
{
}
}
Leslie
Phyllis
Nan
2000
2020
2040
reversePrint(arr
+ 1, +size
2040
1 –01 );
cout << arr[0] << “\n”;
void
reversePrint(string arr[ ], int size)
}
{
names
arr
[0]
arr
[1]
[0]
arr
[2]
[0]
[1]
arr
[0]
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
main()
arr 2000 {
size
string names[3];
3
...
reversePrint(names,3);
}
46
Working
Through
Recursion
if (size == 0) // an empty array
arr 2040
void reversePrint(string arr[ ], int size)
{
return;arr [0] is
else
“ Nan ”
{
}
size 1
names
arr
[0]
arr
[1]
[0]
arr
[2]
[0]
[1]
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
void
reversePrint(string arr[ ], int size)
}
{
if (size == 0) // an empty array
return; arr [0] is
“ Phyllis ”
else
{
}
}
}
Nan
2040
size 2
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
if (size == 0) // an empty array
return; arr [0] is
“ Leslie ”
else
{
Phyllis
2020
arr 2020
void
reversePrint(string arr[ ], int size)
}
{
Leslie
2000
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
main()
arr 2000 {
string names[3];
size 3
...
2000 3
reversePrint(names,3);
}
47
Example #3: Recursion on a Linked List
When we process a linked list using recursion, it’s
very much like processing an array using strategy #2!
head
1200
struct Node
{
};
int val;
Node *next;
There are two differences:
1.
Instead of passing in a pointer to an array element,
you pass in a pointer to a node
2. You don’t need to pass in a size value for your list
(this is determined via the next pointers)
Let’s see an example. We’ll write a function that finds the
biggest number in a NON-EMPTY linked list.
val
42
next
1300
…
val
18
next
1400
val
39
next
nullptr
48
Step #1: Write the function header
Figure out what argument(s)
your function will take and what
it needs to return (if anything).
struct Node
{
};
int val;
Node *next;
To find the biggest item in a linked list,
what kind of parameter should we pass
to our function?
Right! All we need to pass in is a
pointer to a node of the linked list.
Our function will return the biggest
value in the list, so we can make the
return type an int.
So far, so good. Let’s go on to step #2.
int biggest( Node *cur )
{
}
49
Step #2: Define your magic function
Pretend that you are given a
magic function that finds the biggest
value in a linked list and returns it…
There’s only one catch! You are
forbidden
from
inthe
a full linked
Well, if
cur passing
points to
list with
all n elements
to this function.
top node
of an n-element
linked list…
So you can’t use it to find the
Then
cur->next
a list
biggest
item points
in the to
entire
linked
with
(onelist
with
all n-1
n items)…
elements…
Let’s see how to do this.
{
int val;
Node *next;
Now, how can we somehow
isolate
just
n-1
nodes
//
provided
for
your
use! from our
linked
list so we can*n)
pass
int
magicbiggest(Node
{ …them
}
into our magic function?
};
int biggest( Node *cur )
{
}
n-1nelements
elements
But you can use it
to find the biggest
item in a partial list
(e.g., with n-1 elements)!
struct Node
So if we trust our magic
function, this will give us
the biggest item in the
last n-1 nodes of our
linked list.
int main()
{
}
Node *cur = createLinkedList();
int biggest
cur->next
int
biggest==magicbiggest(
magicbiggest(
cur ); );
50
Step #3: Add your base case Code
Determine your base case(s) and write the
code to handle them without recursion!
For this problem, we’re assuming that
the user must pass in a linked list with
at least one element.
So, what’s the simplest case that our
function must handle?
Well, if a linked list has only one node…
struct Node
{
};
int val;
Node *next;
// provided for your use!
int magicbiggest(Node *n) {
…
}
int biggest( Node *cur )
{
if (cur->next == nullptr) // the only node
return cur->val; // so return its value
Nope… Since we don’t have to
deal with an empty list, this is
the simplest case…
}
Then by definition that node must hold
the biggest (only!) value in the list, right?
Are there any other base cases?
int main()
{
}
Node *cur = createLinkedList();
int biggest = magicbiggest(cur->next);
51
Step #4: Solve the problem using the magic function
Now try to figure out how to use the
magic function in your new function to
help you solve the problem.
Unfortunately, you can’t use the magic
Hopefully,
weallcan
agree
function
to process
n nodes
of the list.
that if the magic
So let’s break our problem into two (or
function
what it’s
more)
simplerdoes
sub-problems
and use our
magic
function to
solve those.
supposed
to…
struct Node
{
};
// provided for your use!
int magicbiggest(Node *n) {
{
Then our new function
Use the magic function to process
will work correctly!
the last n-1 elements of the list,
}
if (cur->next == nullptr) // the only node
return cur->val; // so return its value
int largest
int
rest = = magicbiggest(cur);
// pick biggest of 1st node and last n-1 nodes
ignoring the first element.
Then return the full result.
…
int biggest( Node *cur )
Strategy for Linked Lists:
Once you get a result from the
magic function for the last n-1
nodes, combine it with the first
element in the list.
int val;
Node *next;
}
return max( rest , cur->val );
int main()
{
}
Node *cur = createLinkedList();
int biggest = magicbiggest(cur->next);
magicbiggest(cur->next );
52
magicbiggest(Node *n)
Step #5: Remove theint
magic
{
OK, so let’s see what this magic
function really looks like!
Wait a second! Our magicbiggest
But iffunction
our function
callsbiggest!
itself, what
just calls
stops it from running on forever?
That means our biggest function is
just case
calling
itself!
Right!really
Our base
code
ensures
that our function eventually stops
The magic function hid this from us,
calling itself!
but that’s what’s really happening!
OK, well in that case, let’s replace
our calls to the magic function with
calls directly to our own function.
Will that work? Yup!
Woohoo! We’ve just created our
third recursive function!
}
return biggest(n);
struct Node
{
};
int val;
Node *next;
// provided for your use!
int magicbiggest(Node *n) {
…
}
int biggest( Node *cur )
{
if (cur->next == nullptr) // the only node
return cur->val; // so return its value
int rest = magicbiggest(cur->next);
// pick biggest of 1st node and last n-1 nodes
}
return max( rest , cur->val );
int main()
{
}
Node *cur = createLinkedList();
int biggest = magicbiggest(cur->next );
53
Step #6: Validating
our Function
Each time our function runs, its
head2
1200
supposed
to return the biggest
Again, start by testing your
function
int biggest( Node *cur )
1200
value
of the list
that
was input we could pass
The
simplest
to
val
with
the
simplest
possible
input.
{
cur->
passed
in
to
it.function would
our
be an empty
52
52
Our==
function
if (cur->next
nullptr) is
// supposed
the only node to return
next
linked list…
Next,
test
your function
with
the
biggest
value
from
the list has
thatno idea that
return
cur->val;
//
so return
its
value
42
(Our
function
head1 1400
1200
incrementally morewas
complex
inputs.
passed
in.
it’s looking at the tail-end of a
in this problem, we don’t
); linked list However,
int rest = biggest( cur->next
longer
– it just sees
Since the list pointed to by cur only need to check for this case
In this case,
the So
listour
starting simplest
at cur.) input
1200
has
one
by definition,
thisnext
node
1400
return max
( rest,
); thorough,
Excellent!
Now
ifnode,
wecur->val
were
really
we
would our specification val
because
stated
cur->next == nullptr
cur->
isinathe
listbiggest
with athat
single
node.
the biggest
value
the
list!
also
verify holds
our function
works
when
value
all
input lists will 200
have
Since
the
list
pointed
to
by
cur
15
}
42
indicating that this is the only
is in the second
node…
at least one item!next
only has
one node, by definition,
nodeSoinit’s
thecorrect
linked list.
to return
its
value.
Let’sfor
validate
our function
nullptr
(I’ll leave that as an that
exercise
you)
oneOur
node
must
hold the
In this
case,
the
first/top
node’s
next
simplest
input
is
on such a list.
CORRECT BEHAVIOR!
CHECK!
biggest
in the
list!
value
(52)avalue
islist
larger
than
the
biggest
with
two
nodes.
And
this
is
the
correct
result!
int
main()
int biggest( Node *cur )
value
from
the
rest
of
the
list
(42).
So our func{returns its value.
{
Let’s validate
our function
Node CHECK!
*head
= =nullptr;
// empty list
*head1
mkLstWith1Item();
if (cur->next == nullptr) // the So
only node
our function
returns
the
first
on such a list.
return cur->val;
// so return its value
15
node’s value as the biggest.
cout << biggest( head
head1);
);
cur->next );
int rest = biggest(
42
Node *head2 = mkLstWith2Items();
return max ( rest, cur->val );
cout << biggest( head2 );
}
}
54
int biggest(Node *cur)
{
Biggest-in-List
Trace-through
if (cur->next == nullptr)
return(cur->val);
8
int rest = biggest( cur->next );
}
return max( rest, cur-val );
int biggest(Node *cur)
{
if (cur->next == nullptr)
return(cur->val);
1400
int rest = biggest(
cur->next
);
8
}
cur
1300
cur
1400
main()
{
if (cur->next == nullptr)
return(cur->val);
Node *head;
1300
int rest = biggest(
cur->next );
12
}
1200
return max( rest, cur-val
);
12
int biggest(Node *cur)
{
cur
... // create linked list
1200
cout << biggest(head);
return max( rest, cur-val
3 );
}
55
Recursion Challenge #3
Write a recursive function called count that counts the
number of times a number appears in an array.
main()
{
const int size = 5;
int arr[size] = {7, 9, 6, 7, 7};
}
cout << countNums(arr,size,7);
// should print 3
56
Recursion Challenge #3
Step #1: Write the function header
Write a recursive
function called
count that counts
the number of
times a number
appears in an array.
Step #2: Define your magic function
Step #3: Add your base case code
Step #4: Solve the problem using
your magic function
Step #5: Remove the magic!
Step #6: Validate your function
// just assume it works (like magic)!
void magiccount (int a [], int s, int v) {…}
int count (int arr[], int size, int val)
{
}
if (size == 0)
return 0;
int total;
total = magic count (arr+1,size-1,val);
if (arr[0] == val)
++total;
return total;
int main()
{
const int size = 5;
int arr[size] = {7, 9, 6, 7, 7};
int val = 7;
int b = magiccount(arr+1,size-1,val);
count(arr,0,val);
int c = count(arr,1,val);
}
57
Recursion Challenge #4
Write a function that finds and returns the earliest
position of a number in a linked list. If the number is not
in the list or the list is empty, your function should
return -1 to indicate this.
main()
{
Node *cur = <make a linked list>;
cout << findPos(cur,3); // prints 0
cout << findPos(cur,8); // prints 2
cout << findPos(cur,19); // prints -1
}
58
Recursion
Challenge #4
Step #1: Write the function header
Step #2: Define your magic function
Step #3: Add your base case code
Step #4: Solve the problem using
your magic function
Step #5: Remove the magic!
Step #6: Validate your function
Write a function that finds
and returns the earliest
position of a number in a
linked list. If the number is
not in the list or the list is
empty, your function should
return -1 to indicate this.
void magicfindPos(Node *n, int v) {…}
int findPos(Node *cur, int val)
{
if (cur == nullptr) // # is not in list!
return -1;
// so return -1
if (cur->value == val)
return 0;
// # found in top node
int posInRestOfList =
magic findPos(cur->next, val);
}
if (posInRestOfList == -1)
return -1; // # was not in tail
else
return posInRestOfList + 1;
int main()
{
Node *head1
= nullptr;
// empty
*cur = <make
a linked
list>;
cout << findPos(head1, 5);
int val = 3; // let’s find this
Node *head2 = createSingleNode(5);
cout
<< findPos(head2,
// search
the last n-1 5);
nodes for
// your
value =using
the magic func
Node
*head3
createTwoNodes(5,6);
int a<<= findPos(head3,
magicfindPos( cur->next,
val );
cout
6);
}
59
Be Careful with Recursion
This function uses just two
4-byte memory slots for n and i
no matter how big n is.
Be careful – recursion
beefficient!
a pig when
That’scan
very
n
1000
i
// prints from n down-to 0
// without recursion!
Moral:
n
997
// prints from n down-to 0
it comes
to memory
// with
recursion! usage!
// prints from n down-to 0
n)
// void
withprintNums(int
recursion!
{
// prints
from n down-to 0
void
printNums(int
n)
// with if
recursion!
(n <n0)
return;0
{
// prints
from
down-to
n)
//void
withprintNums(int
recursion!
cout
<<
n
<<
“\n”;
if (n < 0) return;
{
printNums(n-1);
void printNums(int
n)
cout
<<
n
<<
“\n”;
if} (n < 0) return;
{
printNums(n-1);
<< nreturn;
<< “\n”;
} cout
if
(n < 0)
printNums(n-1);
} cout << n << “\n”;
printNums(n-1);
}
998
n when
Be
careful
using
void printNums(int n)
999
{
recursion andn never
let your
int i;
recursive calls
get too deep!
n 1000
for (i=n; i >= 0; i--)
cout << i << “\n”;
}
The recursive version creates a whole new
int main()
variable for every level of recursion. int main()
{
}
{
That could be megabytes of wasted data! printNums(1000);
printNums(1000);
}
60
Let’s see some REAL examples!
Ok, so we’ve seen some simple examples of recursion…
But we could more easily solve all of them with for-loops!
Let’s see some examples where recursion really shines!
61
Recursion: Binary Search
Notice how Binary
Search code
recurses on either
Goal:
Search
a sorted array of data for a particular item.
the
first
half *or*
the second half of
Idea:
Use recursion
to quickly find an item within a sorted
the array…
But
never
both. This is
array.
for efficiency.
Algorithm:
What is
the base
case?
Search(sortedWordList, findWord)
{
If (there are no words in the list)
We’re done: NOT FOUND!
Select middle word in the word list.
If (findWord == middle word)
We’re done: FOUND!
Where is the
simplification
code?
If (findWord < middle word)
Search( first half of sortedWordList );
Else // findWord > middle word
Search( second half of sortedWordList );
}
62
Binary Search: C++ Code
Here’s a real binary search implementation in C++. Let’s see how it works!
int BS(string A[], int top, int bot, string f)
{
if (top > bot)
return (-1);
// Value not found
else
{
int Mid = (top + bot) / 2;
if (f == A[Mid])
return(Mid); // found – return where!
else if (f < A[Mid])
return(BS(A,top,Mid - 1,f));
else if (f > A[Mid])
return(BS(A, Mid + 1,bot,f));
}
}
As Binary
our binary search
Recursion:
Search
progresses, top will get bigger
63
and bot will get smaller.
If they ever pass each other,
it means that our item was not
in the array.
top
0
1
2
3
4
Albert
Brandy
Carol
David
Eugene
Frank
Gordon
Grendel
Hank
Wayne
Yentle
Mid 5
int BS(string A[], int top, int bot, string f)
6
{
7
if (top > bot)
return (-1);
// (0
Value
8
+ 10) not
/ 2 found
else
Which is… 5
9
{
“David” == “Frank”
int Mid = (top + bot) / 2;
bot 10
if (f == A[Mid])
“David” < “Frank”
return(Mid); // found – return where!
main()
else if (f < A[Mid])
return(BS(A,top,Mid - {
1,f));
string names[11] = {“Albert”,…};
else if (f > A[Mid])
return(BS(A, Mid + 1,bot,f));
if (BS(names,0,10,”David”) != -1)
}
cout << “Found it!”;
}
}
Recursion: Binary Search
64
top
0
1
Albert
Brandy
Carol
David
Eugene
Frank
Gordon
Grendel
Hank
Wayne
Yentle
int BS(string 0A[],
Mid 2
> 4 int top, int bot, string f)
{
3
if (top > bot)
4
return (-1);
// (0
Value
+ 4) /not
2 found
else
Mid 5
Which is… 2
int {BS(string A[],
int ==
top,
int bot, string f)
“David”
“Carol”
6
int Mid = (top + bot) / 2;
{
7
if (top
bot)
if (f> ==
A[Mid])
“David” < “Carol”
return
(-1);
//
found where!
return(Mid);
// Value
found not
– return
8
else
else if (f < “David”
A[Mid])> “Carol”
9
{
return(BS(A,top,Mid - 1,f));
int
Mid
(top
+ bot) / 2;
bot 10
else
if= (f
> A[Mid])
return(BS(A,
if (f
== A[Mid]) Mid + 1,bot,f));
} return(Mid); // found – return where!
main()
} else if (f < A[Mid])
return(BS(A,top,Mid
1,f));
0
4 - {
string names[11] = {“Albert”,…};
else if (f > A[Mid])
return(BS(A, Mid + 1,bot,f));
if (BS(names,0,10,”David”) != -1)
}
cout << “Found it!”;
}
}
Recursion: Binary Search
65
top
0
1
Albert
Brandy
Carol
David
Eugene
Frank
Gordon
Grendel
Hank
Wayne
Yentle
int BS(string A[], int top, int bot, string f)
Mid 2
int BS(string 3A[],
int
top,
int
bot,
string
f)
>4
{
Mid
3
{
if (top > bot)
if (top > bot)
bot 4
return (-1);
// Value not found
return (-1);
Value
(3
+ int
4) /not
2 found
int
BS(string
A[], int//top,
bot,
string f)
else
5
else
{
Which
is…
3
int {BS(string A[], int top, int bot, string f)
{ (top > bot) “David” == “David”
6
Mid = (top + bot) / 2;
{ ifint
int Mid
= (top //
+ bot)
/ not
2; found
return
(-1);
Value
7
if (top
>
bot)
if (f == A[Mid])
if (f(-1);
== A[Mid]) Value not found
else
return
return(Mid); //
// found – return where!
8
return(Mid);
//
found
–
return
where!
3
{
else
else if (f < A[Mid])
9
else
< A[Mid])
int
Midif= (f
(top
+ bot) /-2;
{
return(BS(A,top,Mid
1,f));
int
Mid
= (f
(top
+ bot) / 2;- 1,f));
if
(freturn(BS(A,top,Mid
==
A[Mid])
10
else
if
> A[Mid])
else
if
(f
>
A[Mid])
return(Mid);
– return where!
return(BS(A,
Midfound
+ 1,bot,f));
if (f
== A[Mid]) //
return(BS(A,
Mid + 1,bot,f));
if (f < A[Mid])
}else
return(Mid);
// found – return where!
}
-main()
1,f));
} else return(BS(A,top,Mid
if (f < A[Mid])
} else if (f > A[Mid])
return(BS(A,top,Mid - {
1,f));
Mid3 + 1,bot,f));
4string names[11] = {“Albert”,…};
else return(BS(A,
if (f > A[Mid])
} return(BS(A, Mid + 1,bot,f));
if (BS(names,0,10,”David”) != -1)
} }
cout << “Found it!”;
}
}
Recursion: Binary Search
66
top
0
1
Mid 2
3
Albert
Brandy
Carol
David
Eugene
Frank
Gordon
Grendel
Hank
Wayne
Yentle
bot 4
int BS(string A[], int top, int bot, string f)
5
{
int BS(string A[], int top, int bot, string f)
6
{ if (top > bot)
return
(-1);
// Value not found
7
if (top
> bot)
else
return (-1);
// Value not found
8
{
else
9
{ int Mid = (top + bot) / 2;
int
= (top
+ bot) / 2;
if Mid
(f ==
A[Mid])
10
return(Mid);
if (f
== A[Mid]) // found – return where!
else
if (f < A[Mid])
return(Mid);
// found – return where!
-main()
1,f));
else return(BS(A,top,Mid
if (f < A[Mid])
else
if (f > A[Mid]) - {
return(BS(A,top,Mid
1,f));
string names[11] = {“Albert”,…};
Mid3 + 1,bot,f));
else return(BS(A,
if (f > A[Mid])
} return(BS(A, Mid + 1,bot,f));
if (BS(names,0,10,”David”) != -1)
} }
cout << “Found it!”;
}
}
Recursion: Binary Search
67
top
0
1
2
3
4
Albert
Brandy
Carol
David
Eugene
Frank
Gordon
Grendel
Hank
Wayne
Yentle
Mid 5
int BS(string A[], int top, int bot, string f)
6
{
7
if (top > bot)
return (-1);
// Value not found
8
else
9
{
int Mid = (top + bot) / 2;
bot 10
if (f == A[Mid])
return(Mid); // found – return where!
main()
else if (f < A[Mid])
return(BS(A,top,Mid - {
1,f));
string names[11] = {“Albert”,…};
else if (f > A[Mid])
return(BS(A, Mid
3 + 1,bot,f));
if (BS(names,0,10,”David”) != -1)
}
cout << “Found it!”;
}
}
68
And then write a “helper function” to
Recursion
Helper
actually do the complex
recursion withFunctions
complex
So
we justparameters…
saw a recursive version of Binary Search:
int BS(string A[], int top, int bot, string f)
{
...
}
In these cases, you’ll want to define a
Notice how many crazy parameters it takes?
function
simple interface
What is top? What’s bot? That’s going
to bewith
reallya confusing
for the user!
(earow-to-understand parameters)
You can then call your helper function
Wouldn’t it be nicer if we just provided
our simple
user with
a simple
from your
function…
function (with a few, obvious params) and then hid the complexity?
int SimpleBinarySearch(string A[], int size, string findMe)
{
return BS(A , 0 , size-1 , findMe);
}
This simple function can then call the complex recursive function to
do the dirty work, without confusing the user.
69
Solving a Maze
We can also use recursion to find a solution to a maze.
In fact, the recursive solution works in the same basic way
as the stack-based solution we saw earlier.
The algorithm uses recursion to keep moving down paths
until it hits a dead end.
Once it hits a dead end, the function returns until it finds
another path to try.
This approach is called “backtracking”
70
a the
Maze
Ah! ButSolving
we did simplify
bool solvable; // globals
int dcol, drow;
problem!
char m[11][11] = {
Start "**********",
2 == 10
&& col)
1 == 10??
We dropped
a breadcrumb!
"*#
*",
void
solve(int
row,
int
"*# * * ** *",
{
"*** * * *",
m[row][col]
= there’s
‘#’; //one
drop crumb
This
ensures that
"* * ** * *",
ifsquare
(row ==
&& col == dcol)
less
fordrow
our recursive
"*
*** *",
solvable
= true; // done!
call to investigate.
"* *
* *",
if (m[row-1][col] == ' ‘)
"* ***** *",
1 == int
10 &&col)
1 == 10??
solve(row-1,col);
void solve(int
row,
"*
* *",
if (m[row+1][col] == ' ‘)
{
"**********“
solve(row+1,col);
m[row][col]
= ‘#’; // drop crumb
(m[row][col-1]
== ==
' ‘)
ifif
(row
== drow && col
dcol)
OK };
– so we’re aboutFinish
to
solve(row,col-1);
solvable
= true; // done!
recurse.
main()
(m[row][col+1]
' ‘)
ifif
(m[row-1][col]
====
' ‘)
{
But wait, have we
solve(row,col+1);
solve(row-1,col);
solvable
= false;
simplified
the problem
} //(m[row+1][col]
if
== ' ‘)
dead end! – BACKTRACK!
drow
= dcolcall?
= 10;
solve(row+1,col);
for our
recursive
2
1
if (m[row][col-1] == ' ‘)
If solve(1,1);
not, won’t
1 1our
solve(row,col-1);
if (solvable
== true)
function
run forever?
if (m[row][col+1] == ' ‘)
cout << “possible!”
solve(row,col+1);
};
}
71
Solving a Maze
void solve(int row, int col)
bool solvable; // globals
{
int dcol, drow;
m[row][col] = ‘#’; // drop crumbchar m[11][11] = {
if (row == drow && col == dcol)
Start "**********",
solvable = true; // done!
"*###
*",
if (m[row-1][col] == ' ‘)
"*# *#* ** *",
1 ==
10 &&
3 ==
10??
solve(row-1,col);
void solve(int
row,
int
col)
"*** * * *",
{ if (m[row+1][col] == ' ‘)
"* * ** * *",
solve(row+1,col);
1row,
==
&& 2//
== drop
10?? crumb
= 10
‘#’;
void m[row][col]
solve(int
int
col)
"*
*** *",
if
(m[row][col-1]
==
'
‘)
if (row == drow && col == dcol)
{
"* *
* *",
1
1
solve(row,col-1);
solvable
true;
//
done!
= =‘#’;
drop
crumb
"* ***** *",
voidm[row][col]
solve(int
row,
int//
col)
if(m[row-1][col]
(m[row][col+1]
====
‘)
==
' '‘)
if if
(row
== drow && col
dcol)
"*
* *",
{
solve(row,col+1);
solve(row-1,col);
solvable
true;
done!
"**********“
m[row][col]
==‘#’;
////
drop
crumb
}if
Finish
(m[row+1][col]
'dcol)
‘)
(m[row-1][col]
== ==
' ‘)
};
ifif
(row
== drow && col
==
solve(row+1,col);
2
3 done!
solve(row-1,col);
solvable
= true;
//
if
(m[row][col-1]
' ‘)
(m[row+1][col]
' ‘)
main()
ifif
(m[row-1][col]
====
' ==
‘)
solve(row,col-1);
solve(row+1,col);
{
solve(row-1,col);
if
(m[row][col+1]
' ‘)
(m[row][col-1]
' ‘)
solvable = false;
ifif
(m[row+1][col]
====
' ==
‘)
solve(row,col+1);
solve(row,col-1);
drow = dcol = 10;
solve(row+1,col);
}(m[row][col-1]
(m[row][col+1]
' ‘)
ifif
====
' ‘)
solve(row,col+1);
1
3
solve(1,1);
solve(row,col-1);
} (m[row][col+1] == ' ‘)
if (solvable == true)
if
cout << “possible!”
solve(row,col+1);
1
2
};
}
And on it goes…
72
Solving a Maze
void solve(int row, int col)
bool solvable; // globals
2
3
{
int dcol, drow;
m[row][col] = ‘#’; // drop crumbchar m[11][11] = {
if (row == drow && col == dcol)
Start "**********",
solvable = true; // done!
"*########*",
if (m[row-1][col] == ' ‘)
"*# *#*#**#*",
3 ==
10 &&
1 ==
10??
solve(row-1,col);
void solve(int
row,
int
col)
"***# *##*#*",
{ if (m[row+1][col] == ' ‘)
"*# *# **#*#*",
solve(row+1,col);
2row,
==
&& 1//
== drop
10?? crumb
= 10
‘#’;
void m[row][col]
solve(int
int
col)
"*#### ***#*",
if
(m[row][col-1]
==
'
‘)
if (row == drow && col == dcol)
{
"*## *### *# *",
1
1
solve(row,col-1);
solvable
true;
//
done!
= =‘#’;
drop
crumb
"*## *****#*",
voidm[row][col]
solve(int
row,
int//
col)
if(m[row-1][col]
(m[row][col+1]
====
‘)
==
' '‘)
if if
(row
== drow && col
dcol)
"*##### * #*",
{
solve(row,col+1);
solve(row-1,col);
solvable
true;
done!
"**********“
m[row][col]
==‘#’;
////
drop
crumb
}if
Finish
(m[row+1][col]
'dcol)
‘)
(m[row-1][col]
== ==
' ‘)
};
ifif
(row
== drow && col
==
solve(row+1,col);
solve(row-1,col);
solvable
= true; // done!
if
(m[row][col-1]
' ‘)
(m[row+1][col]
' ‘)
main()
ifif
(m[row-1][col]
====
' ==
‘)
solve(row,col-1);
solve(row+1,col);
{
solve(row-1,col);
if
(m[row][col+1]
' ‘)
(m[row][col-1]
' ‘)
solvable = false;
ifif
(m[row+1][col]
====
' ==
‘)
solve(row,col+1);
solve(row,col-1);
drow = dcol = 10;
solve(row+1,col);
}(m[row][col-1]
(m[row][col+1]
' ‘)
ifif
====
' ‘)
solve(row,col+1);
solve(1,1);
solve(row,col-1);
} (m[row][col+1] == ' ‘)
if (solvable == true)
if
cout << “possible!”
solve(row,col+1);
};
}
And on it goes…
Writing a TicTacToe Player
73
bool someoneWonOrItWasATieGame(board)
{
if (X has three in a row) // X wins
return true;
if (O has three in a row) // O wins
return true;
if (all squares are filled) // tie game
return true;
}
return false;
Have you ever wondered
how to build an intelligent
chess player?
Let’s learn how – but for
simplicity, we’ll look at
TicTacToe!
GameBoard b;
while (someoneWonOrItWasATieGame() == false)
{
move = GetPlayerMove(b);
b.applyMove(move);
}
move = FindBestCompMove(b);
b.applyMove(move);
74
Writing a TicTacToe Player
X XO
XO
First, let’s see what
our game looks like
at a high level.
OK, now let’s see how
the FindBestCompMove
function works!
Human:
“I want to put my
intothe
theupperleftXXinto
middle
spot.”
left corner.”
Computer:
“I want to put my
GameBoard b;
upperO into the middle
while (someoneWonOrItWasATieGame() == false)
right
spot.”
spot.”
{
move = GetPlayerMove(b);
b.applyMove(move);
}
move = FindBestCompMove(b);
b.applyMove(move);
75
Writing a TicTacToe Player
X XO
XO
The big picture:
This function tries each possible move for
O, and for each, plays out the entire game
to see what would happen.
O
It then returns the move that results in the
best possible outcome for O.
FindBestCompMove(board)
{
for each legal move j the computer can make
duplicate the current board and apply move j
if (theComputerJustWon() == true)
score[j] = -1;
else if (itsATieGame() == true)
score[j] = 0;
else // not sure yet what the result will be…
score[j] =FindBestHumanMove();
return the lowest {score,move}
}
GameBoard b;
while (someoneWonOrItWasATieG
{
move = GetPlayerMove(b);
b.applyMove(move);
}
move = FindBestCompMove(b);
b.applyMove(move);
76
The big picture: Player
Writing a TicTacToe
This function tries each possible move for
X, and for each, plays out the entire game
to see what would happen.
score[0] = +1
The simulated human
just WON by making
It then returns the move that results in the
this move!
best possible outcome for X.
X XO
XO
X XO
XOO
FindBestHumanMove(board)
{
The computer
has
X
This
move didn’t
for each legal move j the human can make
not
woninbya making
result
tieduplicate the current board and apply move j
FindBestCompMove(board)
thiseither…
move…
game
if (theHumanJustWon() == true)
{
GameBoard b;
score[j]
=
+1;
for each legal move j the computer can make
else if (itsATieGame()
== true)
while (someoneWonOrItWasATieG
duplicate
the current board
and apply move j
score[j] = 0;
{
if (theComputerJustWon() == true)
else // not sure yet what the result will be…
move = GetPlayerMove(b);
score[j] = -1;
score[j] =FindBestCompMove();
else if (itsATieGame() == true)
b.applyMove(move);
return the highest {score,move}
score[j] = 0;
}
else // not sure yet what the result will be… OK, so let’s
see
what’s the worst
move
= FindBestCompMove(b);
score[j] =FindBestHumanMove();
a simulated
human could do to us
b.applyMove(move);
return the lowest {score,move}
if the computer
made this move…
}
}
77
Writing a TicTacToe Player
score[0] = +1
X XO
XO
X XO
XOO
FindBestCompMove(board)
{
for each legal move j the computer can make
X X O X X O score[0] = -1
duplicate the current board and apply move j
FindBestHumanMove(board)
XOO XOO
if
(theComputerJustWon()
==
true)
{
X
X
score[j]
= -1; j the human can make
for each
legal move
else if (itsATieGame()
== true)
duplicate
the current board
and apply move j
FindBestCompMove(board)
score[j]
=
0;
if (theHumanJustWon() == true)
{
GameBoard b;
else
// not=sure
yet what the result will be…
score[j]
+1;
for each
legal move
j the computer can make
score[j]
=FindBestHumanMove();
else
if
(itsATieGame()
== true)
Owhile
(someoneWonOrItWasATieG
duplicate
the
current
board
and apply move j
The
This
simulated
move didn’t
human
return
the
lowest
{score,move}
score[j] = 0;
{
(theComputerJustWon() == true)
} ifelse
did
result
not in
win
a tie-game
by making
// not sure yet what the result will be…
movesee
= either…
GetPlayerMove(b);
score[j] = -1;
OK, so let’s
what’s
the worst
this
move…
score[j] =FindBestCompMove();
else if (itsATieGame() == true)
b.applyMove(move);
the computer
player could do to us
return the highest {score,move}
score[j] = 0;
Thethis
computer
if the human made
move… player
}
else // not sure yet what the result will be…
would have just WON
move = FindBestCompMove(b);
score[j] =FindBestHumanMove();
by making this move!
b.applyMove(move);
return the lowest {score,move}
}
}
78
Writing
a
TicTacToe
Player
X XO
In this case, both outcomes
We’ve finished evaluating
XO
from the purple board are theThe computer now all possible legal moves
same – the computer wins! knows all possible
from this board.
outcomes
that can
score[0]
= +1 X
So we’ll inform the level above
emerge from
this board.
score[1]
= -1 X
us that if the human puts an X
in the bottom-middle spot, the
FindBestCompMove(board)
{
computer will always win!
XO
OO
for each legal move j the computer can make
X X O X X O score[0] = -1
duplicate the current board and apply move j
score[1] = -1
FindBestHumanMove(board)
O
O
O
O
X
X
if (theComputerJustWon() == true)
{
X
X
score[j]
= -1; j the human can make
for each
legal move
else if (itsATieGame()
== true)
duplicate
the current board
and apply move j
FindBestCompMove(board)
X XO
score[j]
=
0;
if (theHumanJustWon() == true)
{
GameBoard
b;
else
// not=sure
yet what the result will be…
O
O
X
score[j]
-1;
for each
legal move
j the computer can make
score[j]
=FindBestHumanMove();
else
if
(itsATieGame()
== true)
Owhile
Ois to find
X (someoneWonOrItWasATieG
The
computer’s
goal
duplicate
the
current
board
and apply move j
return
the
lowest
{score,move}
score[j] = 0;
{ lowest-scoring outcome
(theComputerJustWon() == true)
the
} ifelse
// not sure yet what the result will be…
= GetPlayerMove(b);
score[j] = -1;
(thatmove
can be
reached from the
score[j] =FindBestCompMove();
else if (itsATieGame() == true)
purpleb.applyMove(move);
board), as this indicates
return the highest {score,move}
score[j] = 0;
The computer
player
the best the computer
can hope
}
else // not sure yet what the result will be…
would
move = to
FindBestCompMove(b);
do. have just WON
score[j] =FindBestHumanMove();
by making this move!
b.applyMove(move);
return the lowest {score,move}
}
}
79
We’ve finished evaluating
Writing a TicTacToe
Player
all possible legal moves
score[0] = +1
score[1] = -1
score[2] = +1
…
X XO
XO
O
The simulated human
from this board.
now knows all possible
outcomes that can
score[0] = +1 X X O X X O X X
emerge from this board.
score[1] = -1 X O O X O
XO
This process continues until
score[0] = -1
X
score[0]
X O = -1O
O
all possible
at the top
… =0
In this case, while
2 of themoves
outcomes
score[1]
level
have
evaluated and
result in a loss
for
thebeen
human…
X XO X XO
the computer finds its best
FindBestHumanMove(board)
XOO XOO
option.
We’ll
skip
tracing
the
first
move
(lower-left)
results
in
{
X
X
X
through
this
again to
a guaranteed
the can
human.
for each
legal move win
j thefor
human
make
time…move j
duplicate the current boardsave
and apply
FindBestCompMove(board)
XX
X
O
X
O
X
X
O
X
X
if
(theHumanJustWon()
==
true)
{ So we’ll tell the level above us that if
GameBoard
b;
Finally, theX
computer
returns
O O the
O
XOO XO
X
O
score[j]
=
-1;
thelegal
computer
puts
an O in can
themake
for each
move j the
computer
option that
results
in its
else if (itsATieGame()
== true)
X
O
O
Owhile
X best
O OX X
X
(someoneWonOrItWasATieG
duplicate
the current
and apply
middle-right
spot,board
the
human
canmove j
(lowest) possible
outcome…
score[j] = 0;
{
if (theComputerJustWon()
== true)
guarantee
a
win!
The
simulated
human’s goal is to
else // not sure yet what the result will be…
move
= GetPlayerMove(b);
score[j] = -1;
find
the
highest-scoring
score[j] =FindBestCompMove();
And this
is the move
else if (itsATieGame() == true)
b.applyMove(move);
outcome
can be reached
return the highest {score,move}
that is(that
played.
score[j] = 0;
}
purple board), as this
else // not sure yet what the result will be… from the
move
= FindBestCompMove(b);
indicates the
best the human
score[j] =FindBestHumanMove();
b.applyMove(move);
could hope to do.
return the lowest {score,move}
}
}
80
Object-Oriented Design
So, how does a computer scientist
go about designing a program?
How do you figure out all of the
classes, methods, algorithms, etc.
that you need for a program?
At a high level, its best to tackle
a design in two phases:
First, determine the classes
you need, what data they
hold, and how they interact
with one another.
(Well, it’s not earow! Many senior
Mad EE
scientist
engineers
are horrible
at it!)
Second, determine each
class’s data structures
and algorithms.
81
Class Design Steps
1. Determine the classes and objects
required to solve your problem.
2. Determine the outward-facing
functionality of each class. How
do you interact with a class?
3. Determine the data each of your
classes holds and…
4. How they interact with each other.
82
An Example
Often, we start with a textual
specification of the problem.
For instance, let’s consider a
spec for an electronic calendar.
Each user’s calendar should contain appointments for
that user. There are two different types of
appointments, one-time appts and recurring appts.
Users of the calendar can get a list of appointments
for the day, add new appointments, remove existing
appointments, and check other users’ calendars to see
if a time-slot is empty. The user of the calendar
must supply a password before accessing the calendar.
Each appointment has a start-time and an end-time, a
list of participants, and a location.
83
Step #1: Identify Objects
Start by identifying
potential classes.
The easiest way to do this is
identify all of the nouns in the
specification!
Each user
user’s calendar should contain appointments for
that user. There are two different types of
appointments, one-time appts and recurring appts
appts.
Users of the calendar can get a list of appointments
for the day, add new appointments, remove existing
appointments, and check other users’ calendars to see
if a time-slot is empty. The user of the calendar
must supply a password before accessing the calendar.
Each appointment has a start-time and an end-time
end-time, a
list of participants
participants, and a location
location.
84
Step #1b: Identify Objects
Now that we know our nouns,
let’s identify potential classes.
We don’t need classes for every
noun, just for those key
components of our rowstem…
Each user
user’s calendar should contain appointments for
that user. There are two different types of
Which
nouns should
we turn
appointments,
one-time
appts and recurring appts
appts.
into
classes?
Users
of the calendar can get a list of appointments
for the
day, add new appointments, remove existing
Calendar
appointments, and check other users’ calendars to see
if a Appointment
time-slot is empty. The user of the calendar
Recurring
Appointment
must supply
a password before accessing the calendar.
Each appointment
has a start-time and an end-time
end-time, a
One-time
Appointment
list of participants
participants, and a location
location.
85
Step #2a: Identify Operations
Next we have to
determine what actions
need to be performed
by the rowstem.
To do this, we identify all of
the verb phrases in the
specification!
Each user’s calendar should contain appointments for
that user. There are two different types of
appointments, one-time appts and recurring appts.
Users of the calendar can get a list of appointments
for the day, add new appointments
appointments, remove existing
appointments, and check other users’ calendars to see
appointments
if a time-slot is empty. The user of the calendar
must supply a password before accessing the calendar.
Each appointment has a start-time and an end-time, a
list of participants, and a location.
86
Step #2b: Associate Operations w/Classes
Calendar
list getListOfAppts(void)
bool addAppt(Appointment *addme)
bool removeAppt(string &apptName)
bool checkCalendars(Time &slot,
Calendar others[])
bool login(string &pass)
bool logout(void)
Appointment
bool setStartTime(Time &st)
bool setEndTime(Time &st)
bool addParticipant(string &user)
bool setLocation(string &location)
Next we have to determine what
actions go with which classes.
(let’s just look at the first two)
Verbs
get a list of appointments
add new appointments
remove existing
check other users’ calendars
supply a password
has a start-time
has an end-time
has a list of participants
has a location
87
Step #2b: Associate Operations w/Classes
Calendar
Calendar() and ~Calendar()
list getListOfAppts(void)
bool addAppt(Appointment *addme)
bool removeAppt(string &apptName)
bool checkCalendars(Time &slot,
Calendar others[])
bool login(string &pass)
bool logout(void)
Appointment
Appointment() and ~Appointment()
bool setStartTime(Time &st)
bool setEndTime(Time &st)
bool addParticipant(string &user)
bool setLocation(string &location)
NextSo,
we have
to determine
do we
need all what
actions
with
which classes.
ofgoour
classes?
OneTimeAppointment
Of course, our classes need
OneTimeAppointment()
constructors
and destructors!
~OneTimeAppointment()
bool
&st)our
AndsetStartTime(Time
now let’s consider
bool setEndTime(Time
&st)
other two classes.
bool addParticipant(string &user)
bool setLocation(string &location)
RecurringAppointment
RecurringAppointment()
~RecurringAppointment()
bool setStartTime(Time &st)
bool setEndTime(Time &st)
bool addParticipant(string &user)
bool setLocation(string &location)
bool setRecurRate(int numDays)
88
Step 3: Determine Relationships & Data
Now you need to figure out
how the classes relate to each
other and what data they hold.
There are three relationships
to consider:
1. Uses: Class X uses objects of class Y, but may not
actually hold objects of class Y.
2. Has-A: Class X contains one or more instances of
class Y (composition).
3. Is-A: Class X is a specialized version of class Y.
This will help you figure out what private data each class
needs, and will also help determine inheritance.
89
Step 3: Determine Relationships & Data
Calendar
Calendar() and ~Calendar()
list getListOfAppts(void)
bool addAppt(Appointment *addme)
bool removeAppt(string &apptName)
bool checkCalendars(Time &slot,
Calendar others[])
bool login(string &pass)
bool logout(void)
private:
Appointment m_app[100];
String
m_password;
A Calendar contains appointments
A Calendar must have a password
A Calendar uses other calendars,
but it doesn’t need to hold them.
In general, if a class naturally
holds a piece of data, your
design should place the data in
that class.
Of course, you might not get
it right the first time.
In this case, it helps to
“re-factor” your classes.
(i.e. iterate till you get it right)
90
Ahhhh but now
RIGHT! You need a
Appointment is a base
virtual destructor in
class!!! What do we need to
every base class!
make sure we have?
Step 3: Determine Relationships & Data
Appointment
Appointment()
virtual
~Appointment()
~Appointment()
bool setStartTime(Time &st)
bool setEndTime(Time &st)
bool addParticipant(string &user)
bool setLocation(string &location)
private:
Time m_startTime;
Time m_endTime;
string m_participants[10];
string m_location;
Now, how about our
RecurringAppointment?
It’s shares all of the attributes
of an Appointment. So should a
Recurring Appointment contain an
Appointment or use inheritance?
An Appointment has a start time
An Appointment has an end time
An Appointment is associated
with a set of particpants.
An Appointment is held at a
location.
RecurringAppointment
: public Appointment
RecurringAppointment()
~RecurringAppointment()
bool setStartTime(Time &st)
bool setEndTime(Time &st)
private:
bool addParticipant(string &user)
int m_numDays;
bool setLocation(string &location)
bool setRecurRate(int numDays)
91
Step 4: Determine Interactions
Here, we want to determine
how each class interacts with
the others.
The best way to determine the
interactions is by coming up
with use cases…
Use Case Examples
1. The user wants to add an appointment to their calendar.
2. The user wants to determine if they have an
appointment at 5pm with Joe.
3. The user wants to locate the appointment at 5pm and
update it to 6pm.
92
Use Case #1
1. The user wants to add an appointment to their calendar.
Time &start, Time &end,
string loc, string parts[])
A. The user creates a new Appointment object and sets
its values:
Appointment *app = new Appointment; (“10am”,”11am”,”Dodd”,…);
app->setStartTime(“10am”);
app->setEndTime(“11am”);
…
B. The user adds the Appointment object to the Calendar:
Calendar c;
c.addAppointment(app);
It looks like we’re OK here. Although it might be nicer if
we could set the Appointment’s values during construction
93
Use Case #2
2. The user wants to determine if they have an appointment
at 5pm with Joe.
Calendar
Calendar() and ~Calendar()
Hmm… Can we do this with
list getListOfAppts(void)
our classes?
bool addAppt(Appointment *addme)
Appointment
Nope.
ItSo
doesn’t
far,
We’ll
solook
good.
need
liketo
Now,
we
add
can
can
this
find
we
toif
weour
determine
have
Appointment
an appointment
who’s class!
at anat a
particular
appointment?
time… Let’s
Hmmm…
add this!
Calendar c;
...
Appointment *appt;
appt = c.checkTime(“5pm”);
if (appt == NULL)
cout << “No appt at 5pm”;
else if (appt->isAttendee(“Joe”))
cout << “Joe is attending!”;
bool removeAppt(string &apptName)
Appointment() & virtual ~Appointment()
bool checkCalendars(Time &slot,
bool setStartTime(Time
&st)
Calendar others[])
bool
setEndTime(Time
bool
login(string
&pass) &st)
bool
logout(void)
bool
addParticipant(string &user)
bool setLocation(string
&location)
Appointment
*checkTime(Time
&t)
bool isAttendee(string &person)
private:
private:
Appointment
m_app[100];
Time m_startTime;
String
m_password;
Time m_endTime;
string m_participants[10];
string m_location;
94
Class Design Conclusions
First and foremost, class design is an iterative process.
Before you ever start to program your class
implementations, it helps to determine your classes, their
interfaces, their data, and their interactions.
It’s important to go through all of the use cases in order
to make sure you haven’t forgotten anything.
This is something that you only get better at with
experience, so don’t feel bad if its difficult at first!
95
Appendix
96
Recursion: General
Approach
We just saw this case:
“If there’s just one element in the array,
Virtually every recursive function
hasdothe
following
basic
structure:
then
nothing
and just
return.”
For
our N
array
up into
is where
we actually
recursion.”
LinkedList
xx )) split
ProcessSomeData( SomeDataType
vector<int>
x[],
int
) “use
int
xThis
) example,
two equal parts:
{
When we use recursion, a function calls itself
x[0 thru
N/2] and
x[N/2 thru N]
If x is small enough to
trivially
processed
andbe
passes
in a subset
of the original data.
Process it directly and return the result
Otherwise
In several
our previous
example,
this is(x
where
we merged
Break x into
smaller
problems
1, x2, …)
two sorted
together.
Call our functionthe
to solve
each sub-arrays
sub-problem:
result1 = ProcessSomeData(x1,N1)
result2 = ProcessSomeData(x2,N2)
…
Combine the results (result1…resultn) into
a solution to the whole problem
Return the result
}
97
Recursion: Two Categories
Generally speaking, there are two sub-types of recursion:
1. When you break up the data, you split it in two equal halves
and use recursion to process each half.
ProcessSomeData( SomeDataType x[], int N )
{
If x is small enough to be trivially processed
Process it directly and return the result
Otherwise
Break x into several smaller problems (x1, x2, …)
Call our function to solve each sub-problem:
result1 = ProcessSomeData(x1,N1)
two equally-sized sub-problems
result2 = ProcessSomeData(x
2,N2)
…
x[0 thru
N/2] , N/2 )
Combine the results (result1…result
n) into
x[N/2 thru N] , N/2 )
a solution to the whole problem
Return the result
}
98
Recursion: Two Categories
Generally speaking, there are two sub-types of recursion:
2.
1. When
When you
you break
break up
up the
the data,
data, you
you split
remove
it ina two
single
equal
element
halvesand
use
and recursion
use recursion
to process
to process
the remaining
each half. N-1 elements.
ProcessSomeData( SomeDataType x[], int N )
{
If x is small enough to be trivially processed
Process it directly and return the result
Otherwise
Break x into several
two chunks:
smaller
oneproblems
of size 1,(xand
one
, …)of size N-1
1, x 2
Call our function to solve each sub-problem:
result1 = ProcessSomeData(x
Process x[0] without
recursion
1)
1,Nusing
N], N-1 )
result2 = ProcessSomeData(xx[1
)
2,N2thru
…
Combine the results (result1…resultn) into
a solution to the whole problem
Return the result
99
Working
Through
Recursion
if (size == 0) // an empty array
arr 2040
Now it looks like arr has
just one element and
starts at 2040!
void reversePrint(string arr[ ], int size)
{
Cool! It’s if our array
size
starts at location 2020
and only has 2 elements!
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
return;
else
{
}
names
arr
[0]
arr
[1]
[0]
arr
[2]
[0]
[1]
void
reversePrint(string arr[ ], int size)
}
{
if (size == 0) // an empty array
return;
else
{
2020 + 1 1
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
}
}
Nan
2040
size
}
if (size == 0) // an empty array
return;
else
{
2000 + 1
2
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
Phyllis
2020
arr 2020
void
reversePrint(string arr[ ], int size)
}
{
Leslie
2000
main()
arr
{
string names[3];
size
...
2000 3
reversePrint(names,3);
}
Working
Through
Recursion
if (size == 0) // an empty array
arr 2040
100
void reversePrint(string arr[ ], int size)
{
void reversePrint(string arr[ ], int size)
{
arr 2060
return;
if (size == 0) // an empty array
size 1
else return;
size
{
else
reversePrint(arr
+ 1, size – 1 );
{
We’ve hit our base case!
cout
<< arr[0] << “\n”;
reversePrint(arr
+ 1, size – 1 );
}
cout << arr[0] arr[
<< “\n”;
void
reversePrint(string
], int size)
}
We’ve
got an empty array (of
}
{
}
size 0) so we just return!
if (size == 0) // an empty array
arr 2020
return;
size 2
else
{
}
if (size == 0) // an empty array
return;
else
{
}
}
Leslie
Phyllis
Nan
2000
2020
2040
reversePrint(arr
+ 1, +size
2040
1 –01 );
cout << arr[0] << “\n”;
void
reversePrint(string arr[ ], int size)
}
{
names
arr
[0]
arr
[1]
[0]
arr
[2]
[0]
[1]
arr
[0]
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
main()
arr 2000 {
size
string names[3];
2
...
reversePrint(names,3);
}
101
Working
Through
Recursion
if (size == 0) // an empty array
arr 2040
void reversePrint(string arr[ ], int size)
{
return;arr [0] is
else
“ Nan ”
{
}
size
names
arr
[0]
arr
[1]
[0]
arr
[2]
[0]
[1]
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
void
reversePrint(string arr[ ], int size)
}
{
if (size == 0) // an empty array
return; arr [0] is
“ Phyllis ”
else
{
}
}
}
Nan
2040
size
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
if (size == 0) // an empty array
return; arr [0] is
“ Leslie ”
else
{
Phyllis
2020
arr 2020
void
reversePrint(string arr[ ], int size)
}
{
Leslie
2000
reversePrint(arr + 1, size – 1 );
cout << arr[0] << “\n”;
main()
arr 2000 {
string names[3];
size
...
2000 3
reversePrint(names,3);
}
102
Writing a Chess Player
Based on the current board
configuration, the best move
FindBestIdealHumanMove()
the human can make will give
{
an advantage of +2 over the
For each legal move the ideal human could make
computer.
And theboard
move is…
on the
current
TemporarilyBased
make the
move.
configuration,
therespond
best move
See how the
computer would
FindBestComputerMove()
the computer can make will
Undo the move
{
givethat
an advantage
of
+3 overmost
Return
the
move
hurts
the
computer
For
each
legal
move the
computer
can
make
Based
on
all
of
my
possible
human.
And
the moveboard
is…
Based
on
the
current
} Temporarilythe
make themoves,
move and all of the
the best
move
the
See how theconfiguration,
idealopponent’s
human
would
FindBestIdealHumanMove()
possible
So
now Iplayer
know
thatresponses
ifrespond
I move
ideal human could make would give
Undo the move
{
(and
my responses
to him,
my
left-most
pawn, the
most
an
advantage
of
+1
over
the
Return
the
move
that
hurts
the
human
nd
For
each
legal
move
the
ideal
human
could
make
etc.),
my
best
move
islevels
the
2of
likely
result
after
6most
computer.
And the move is…
} Temporarily make
the
pawn
from
the
right
because
playmove.
is that
the
human
will
See how the computer
respond
nowould
how hard
myme!
have
amatter
+1 advantage
over
FindBestComputerMove()
Undo the move
opponent tries, the best he
{
Return
the
move
that
hurts
can
do
isthe
be computer
down
9most
points!
For
each
legal
move
the
computer
can by
make
} Temporarily make the move
See how the ideal human player would respond
Undo the move
Return the move that hurts the human most
}
Human Player
+1h+2c +1h+4h +2c +2c +9c +5h
Computer Player
This would go on and on,
in many cases playing 6
or more levels deep!
103
Appendix
104
I’m
Nancy
I’m
Phyllis
I’m
Leslie
I’m
Harry
Harry
Nan
Reverse-O Yell:
If you’re the last one in line
Yell out your name
Phyllis
Otherwise
Reverse-O Yell:
Give a copy of this note to the next guy
If
you’re
the
last
one
in line
This is the
When they’re done with the note, yell your
out your name
Les
baseYell
case!
This is the
Otherwise
simplifying
case!guy
It
Give a copy of this note
to the next
Reverse-O
Yell:
Reverse-O
Yell:
getsthe
us closer
andyour name
When
note, yell
If If
you’re
thethe
last
oneone
in line
you’re
last
in
line they’re done with
closer to the last
Yell
outout
your
name
Yell
your
name
guy!
Otherwise
Otherwise
Give
a copy
of of
this
note
to to
thethe
next
guyguy
Give
a copy
this
note
next
When
they’re
done
with
thethe
note,
yellyell
your
name
When
they’re
done
with
note,
your
name
105
Working Through Recursion
Reverse-O Yell:
If you’re the last one in line
Yell out your name
Otherwise
Give a copy of this note to the next guy
When they’re done with the note, yell your name
void reversePrint(string array[ ], int pos, int size)
{
{
}
}
if (pos == size-1) // last in line
if (pos == size-1) // last in line
cout << array[pos] << “\n”;
cout << array[pos] << “\n”;
else
{else
{ reversePrint(array, pos + 1, size);
reversePrint(array,
pos + 1, size);
cout << array[pos] << “\n”;
} cout << array[pos] << “\n”;
}
106
“The Lazy Person’s Sort”
99322
2774
61492
17
Lazy Person’s Sort:
It IS really difficult to
comprehend.
Well,
can youeach
thinkperson
of a
In your
algorithm,
Very
good!
small
so the
relies
onmodification
two other people.
But
it DOES
algorithm
canwork!
be used by
single that’s
person?
To sortjust
600a cards
a lot
That’s
the And
leapwhat
you have
tohad
of
people…
if you
make
with
recursion.
to sort
a million
cards?
If you’re handed just one card, then just give it right back.
Split the cards into two roughly-equal piles
Hand one pile to nerdy
student A and say
ask “do
them
toLazy
sort Person’s
it
studly
the
Sort”
myself
Hand the other pile to nerdy
student B and say
ask “do
them
toLazy
sort Person’s
it
the
Sort”
hotmyself
Take the two sorted piles and merge them into a single sorted pile
Holy Moly! That is mind-blowing!
problem
– we’d
Good
point.
It
doesn’t seemThat’s
like it ashould
work,
but
Ok, blow
our
minds,
I don’t
so.
Hmm.
I’ve think
gotI an
idea…
run
out
of
UCLA
people
Prof Nachenberg. can’t think why it wouldn’t!
and would probably have
Can
they
eventhis?
read?
How
about
to start using USC
students.
arr[0]
108
42
int sumArr(int arr[], int n)
{
}
if (n == 0) return 0;
0
0
int rest = sumArr(arr+1,n-1);
return (arr[0]42
+ rest);
Array-summer Trace-through
int sumArr(int arr[], int n)
{
arr[0] arr[1]
20
}
if (n == 0) return 0
0;
int rest = sumArr(arr+1,n-1);
return (arr[0] + rest);
int sumArr(int arr[], int n)
{
}
if (n == 0) return 0;
42 1
42
int rest = sumArr(arr+1,n-1);
return (arr[0]62
+ rest);
arr[0] arr[1] arr[2]
10
int main()
{
int sumArr(int arr[], int n)
{
}
if (n == 0) return
200;42 2
int rest = 62
sumArr(arr+1,n-1);
return (arr[0]72
+ rest);
const int n = 3;
int nums[n] = { 10, 20, 42 };
nums 10 20 42
3
cout << sumArr( nums , n );
}