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);