Recursion, Scope, Function Templates
Download
Report
Transcript Recursion, Scope, Function Templates
Announcements
HW3 grades will be announced this week
HW4 is due this week
Final exam on August 25, 2010, Wednesday at 09:00
Duration is about 140 minutes (+/- 10 minutes)
Comprehensive but last topics will have more weight
2 pages of cheat notes are allowed
I will send classrooms later via email
Extra Recitations will be held next week before the final
I will make an announcement later about the final recitation times.
Recursion, Scope, Function Templates
Not so related to each other
Chapter 10 and partially 11
10.1, 10.3 (skip 10.3.3), 10.4, 11.2
Global Variables (10.4.1)
Have you ever needed a variable that can be referred in all
functions?
Global variables are defined outside of function bodies and
can be used in all functions
but in that cpp file, cannot be used in other cpp files
Global definitions can be shadowed by local definition
if a global variable is redefined in a function, in that function local
one is used, global one cannot be reached
See globaldemo.cpp (not in the book)
Do not use global variables in your homework, but you
have to know what they are!
Hidden Identifiers (10.4.2)
These are Identifiers defined in inner compound blocks
An identifier defined in a block can be used only in this block
See scope.cpp as an example
Same identifier names can be used in nested blocks
shadow rules apply
When an identifier is used, the nearest definition in the
same or outer compound blocks is referred
better to see in an example (scope.cpp)
let’s trace that program
Global identifiers and identifiers defined in compound
blocks can be used together in more complex cases
See this week recitations for an example
Function Templates (11.2)
We have seen Function Overloading before
different functions with same name are possible
but the parameters must be different so the compiler can
differentiate
void dosomething (int a, int b);
void dosomething (int a);
Think about a particular sort algorithm
for different vector item types (comparable item types), we will
need different functions
the only difference is the item type
overloading works but requires different functions that are almost
the same except the item type
Isn’t there a way to make the type parameter?
YES, function templates
Function Templates
An identifier
template <class Type>
void Print(const tvector<Type> & list, int first)
// post: list[first]..list[list.size()-1] printed, one per line
{
int k;
for(k=first; k <= list.size()-1; k++)
{
cout << list[k] << endl;
}
}
In main
tvector<string> wordList;
tvector<int> lengthList;
Print(wordList, 3);
Print(lengthList, 0);
Function Templates
Not only for vectors, but also the ordinary parameter types can be template
template <class T>
void myfunction (T & param)
{
…
}
Templates can be used as local type, but there must be at least one such
parameter too
template <class TT>
void dosomething (tvector<TT> & vec)
{
TT value;
…
}
Templates can be used as the return type, but there must be at least one such
parameter too
template <class TType>
TType myfunction (const tvector<TType> & p)
template <class TType>
TType myfunction (int a)
// Not valid
Recursion (10.1, 10.3)
Recursion is an essential technique in a programming
language
Allows many complex problems to be solved simply
Elegance and understanding in code often leads to better programs:
easier to modify, extend, verify
Sometimes recursion isn’t appropriate. When it performs bad, it can
be very bad!
need knowledge and experience in how to use it. Some
programmers are “recursion” programmers, some are not
Recursion is not a statement, it is a technique!
The basic idea is to get help solving a problem from
coworkers (clones) who work and act like you do
Ask clone to solve a simpler but similar problem
Use clone’s result to put together your answer
looks like calling a function in itself, but should be done very
carefully!
Print words entered, but backwards
Can use a vector, store all the words and print in reverse order
Using a vector is probably the best approach, but recursion works too (see
printreversed.cpp)
void PrintReversed()
{
string word;
if (cin >> word)
{
PrintReversed();
cout << word << endl;
}
}
int main()
{
PrintReversed();
return 0;
}
// reading succeeded?
// print the rest reversed
// then print the word
The function PrintReversed reads a word, prints the word only
after the clones finish printing in reverse order
Each clone runs a copy of the function, and has its own word variable
See the trace on the board
What is recursion?
Not exactly calling a function in itself
although it seems like this
Recursion is calling a “copy” of a function in itself
clone
All local identifiers are declared anew in a clone
when execution order comes back to the caller clone, the values in
that clone is used
Exponentiation
Computing xn means multiplying n numbers
x.x.x.x.x.x ... x (n times)
If you want to multiply only once, you ask a clone to multiply the rest
(xn = x.xn-1)
clone recursively asks other clones the same
until no more multiplications
each clone collects the results returned, do its multiplication and returns the
result
See the trace on board
double Power(double x, int n)
// post: returns x^n
{
if (n == 0)
{
return 1.0;
}
return x * Power(x, n-1);
}
General Rules of Recursion
Although we don’t use while, for statements, there is a
kind of loop here
if you are not careful enough, you may end up infinite recursion
Recursive functions have two main parts
There is a base case, sometimes called the exit case, which does not
make a recursive call
printreversed: having no more input
exponentiation: having a power of zero
All other cases make a recursive call, most of the time with some
parameter that moves towards the base case
Ensure that sequence of calls eventually reaches the base case
we generally use if - else statements to check the base case
not a rule, but a loop statement is generally not used in a recursive
function
Faster exponentiation
double Power(double a, int n)
// post: returns a^n
{
if (n == 0)
{
return 1.0;
}
double semi = Power(a, n/2);
if (n % 2 == 0)
{
return (semi * semi);
}
return (a * semi * semi);
}
Study the code in 10.1.2
Classic examples of recursion
For some reason, computer science uses these examples:
Factorial: we have seen the loop version, now we will see the recursive
one
Fibonacci numbers:
Classic example of bad recursion (will see)
Towers of Hanoi (will not cover)
N disks on one of three pegs, transfer all disks to another peg, never put a
disk on a smaller one, only on larger
Peg#1
#2
#3
Factorial (recursive)
BigInt RecFactorial(int num)
{
if (0 == num)
{
return 1;
}
else
{
return num * RecFactorial(num - 1);
}
}
See 10.3.1 (facttest.cpp) to determine which version (iterative or
recursive) performs better?
almost the same
Fibonacci Numbers
1, 1, 2, 3, 5, 8, 13, 21, …
Find nth fibonacci number
see fibtest.cpp for both recursive and iterative functions and their
timings
Recursion performs very bad for fibonacci numbers
reasons in the next slide
Fibonacci: Don’t do this recursively
int RecFib(int n)
4
// precondition: 0 <= n
// postcondition: returns
3
2
// the n-th Fibonacci number
{
2
1
1
if (0 == n || 1 == n)
{
return 1;
1
0
}
else
{
return RecFib(n-1) + RecFib(n-2);
}
}
Too many unnecessary calls to calculate the same values
How many for 1?
How many for 2, 3?
5
3
1
2
0
1
0
What’s better: recursion/iteration?
There’s no single answer, many factors contribute
Ease of developing code
Efficiency
In some examples, like Fibonacci numbers, recursive solution
does extra work, we’d like to avoid the extra work
Iterative solution is efficient
The recursive inefficiency of “extra work” can be fixed if we remember
intermediate solutions: static variables
Static variable: maintain value over all function calls
Ordinary local variables constructed each time function called
but remembers the value from previous call
initialized only once in the first function call
Fixing recursive Fibonacci
int RecFibFixed(int n)
// precondition: 0 <= n <= 30
// postcondition: returns the n-th Fibonacci number
{
static tvector<int> storage(31,0);
if (0 == n || 1 == n)
return 1;
else if (storage[n] != 0)
return storage[n];
else
{
storage[n] = RecFibFixed(n-1) + RecFibFixed(n-2);
return storage[n];
}
}
Storage keeps the Fibonacci numbers calculated so far, so that when we
need a previously calculated Fibonacci number, we do not need to calculate
it over and over again.
Static variables initialized when the function is called for the first time
Maintain values over calls, not reset or re-initialized in the declaration line
but its value may change after the declaration line.
Recursive Binary Search
Binary search is good for searching an entry in sorted
arrays/vectors
We have seen the iterative approach before
Now recursive solution
if low is larger than high
not found
if mid-element is the searched one
return mid (found)
if searched element is higher than the mid element
search the upper half by calling the clone for the upper half
if searched element is lower than the mid element
search the lower half by calling the clone for the lower half
Need to add low and high as parameters to the function
Recursive Binary Search
int bsearchrec(const tvector<string>& list, const string& key, int low, int high)
// precondition: list.size() == # elements in list
// postcondition: returns index of key in list, -1 if key not found
{
int mid;
// middle of current range
if (low > high)
return -1;
//not found
else
{
mid = (low + high)/2;
if (list[mid] == key)
{
// found key
return mid;
}
else if (list[mid] < key)
{
// key in upper half
return bsearchrec(list, key, mid+1, high);
}
else
{
}
}
}
// key in lower half
return bsearchrec(list, key, low, mid-1);