diff --git a/lectures/07_order_notation_recursion/README.md b/lectures/07_order_notation_recursion/README.md new file mode 100644 index 0000000..be4b706 --- /dev/null +++ b/lectures/07_order_notation_recursion/README.md @@ -0,0 +1,272 @@ +# Lecture 7 --- Order Notation & Basic Recursion + +- Algorithm Analysis, Formal Definition of Order Notation +- Simple recursion, Visualization of recursion, Iteration vs. Recursion +- “Rules” for writing recursive functions, Lots of examples + +## 7.1 Algorithm Analysis + +Why should we bother? + - We want to do better than just implementing and testing every idea we have. + - We want to know why one algorithm is better than another. + - We want to know the best we can do. (This is often quite hard.) +How do we do it? There are several options, including: + - Don’t do any analysis; just use the first algorithm you can think of that works. + - Implement and time algorithms to choose the best. + - Analyze algorithms by counting operations while assigning different weights to different types of operations +based on how long each takes. + - Analyze algorithms by assuming each operation requires the same amount of time. Count the total number of +operations, and then multiply this count by the average cost of an operation. + +## 7.2 Exercise: Counting Example + +- Suppose arr is an array of n doubles. Here is a simple fragment of code to sum of the values in the array: +```cpp +double sum = 0; +for (int i=0; i 0 +1 n == 0 + Computing integer powers is defined as: +n +p = +( +n · n +p−1 p > 0 +1 p == 0 + These are both examples of recursive definitions + +## 7.11 Recursive C++ Functions + +C++, like other modern programming languages, allows functions to call themselves. This gives a direct method of +implementing recursive functions. Here are the recursive implementations of factorial and integer power: + +```cpp +int fact(int n) { + if (n == 0) { + return 1; + } else { + int result = fact(n-1); + return n * result; + } +} +``` + +```cpp +int intpow(int n, int p) { + if (p == 0) { + return 1; + } else { + return n * intpow( n, p-1 ); + } +} +``` + +## 7.12 The Mechanism of Recursive Function Calls + +- For each recursive call (or any function call), a program creates an activation record to keep track of: + – Completely separate instances of the parameters and local variables for the newly-called function. + – The location in the calling function code to return to when the newly-called function is complete. (Who +asked for this function to be called? Who wants the answer?) + – Which activation record to return to when the function is done. For recursive functions this can be +confusing since there are multiple activation records waiting for an answer from the same function. +- This is illustrated in the following diagram of the call fact(4). Each box is an activation record, the solid lines +indicate the function calls, and the dashed lines indicate the returns. Inside of each box we list the parameters +and local variables and make notes about the computation. +- This chain of activation records is stored in a special part of program memory called the stack + +## 7.13 Iteration vs. Recursion + +- Each of the above functions could also have been written using a for or while loop, i.e. iteratively. For example, here is an iterative +version of factorial: + +```cpp +int ifact(int n) { +int result = 1; +for (int i=1; i<=n; ++i) +result = result * i; +return result; +} +``` + +- Often writing recursive functions is more natural than writing iterative functions, especially for a first draft of +a problem implementation. +- You should learn how to recognize whether an implementation is recursive or iterative, and practice rewriting +one version as the other. Note: We’ll see that not all recursive functions can be easily rewritten in iterative +form! +- Note: The order notation for the number of operations for the recursive and iterative versions of an algorithm +is usually the same. However in C, C++, Java, and some other languages, iterative functions are generally +faster than their corresponding recursive functions. This is due to the overhead of the function call mechanism. +Compiler optimizations will sometimes (but not always!) reduce the performance hit by automatically eliminating +the recursive function calls. This is called tail call optimization. + +## 7.14 Exercises + +1. Draw a picture to illustrate the activation records for the function call +cout << intpow(4, 4) << endl; +2. Write an iterative version of intpow. +3. What is the order notation for the two versions of intpow? + +## 7.15 Rules for Writing Recursive Functions + +Here is an outline of five steps that are useful in writing and debugging recursive functions. Note: You don’t have +to do them in exactly this order... +1. Handle the base case(s). +2. Define the problem solution in terms of smaller instances of the problem. Use wishful thinking, i.e., if someone +else solves the problem of fact(4) I can extend that solution to solve fact(5). This defines the necessary +recursive calls. It is also the hardest part! +3. Figure out what work needs to be done before making the recursive call(s). +4. Figure out what work needs to be done after the recursive call(s) complete(s) to finish the computation. (What +are you going to do with the result of the recursive call?) +5. Assume the recursive calls work correctly, but make sure they are progressing toward the base case(s)! + +## 7.16 Location of the Recursive Call — Example: Printing the Contents of a Vector + + Here is a function to print the contents of a vector. Actually, it’s two functions: a driver function, and a true +recursive function. It is common to have a driver function that just initializes the first recursive function call. + +```cpp +void print_vec(std::vector& v, unsigned int i) { +if (i < v.size()) { +cout << i << ": " << v[i] << endl; +print_vec(v, i+1); +} +} +``` + +```cpp +void print_vec(std::vector& v) { +print_vec(v, 0); +} +``` + + What will this print when called in the following code? +```cpp +int main() { +std::vector a; +a.push_back(3); a.push_back(5); a.push_back(11); a.push_back(17); +print_vec(a); +} +``` + How can you change the second print vec function as little as possible so that this code prints the contents +of the vector in reverse order?