# 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 ## 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; i0 exist such that A requires no more than k * f(n) time units (operations) to solve a problem of size n ≥ n0. - For example, algorithms requiring 3n + 2, 5n − 3, and 14 + 17n operations are all O(n). This is because we can select values for k and n0 such that the definition above holds. (What values?) Likewise, algorithms requiring n2/10 + 15n − 3 and 10000 + 35n2 are all O(n2). - Intuitively, we determine the order by finding the asymptotically dominant term (function of n) and throwing out the leading constant. This term could involve logarithmic or exponential functions of n. Implications for analysis: – We don’t need to quibble about small differences in the numbers of operations. – We also do not need to worry about the different costs of different types of operations. – We don’t produce an actual time. We just obtain a rough count of the number of operations. This count is used for comparison purposes. - In practice, this makes analysis relatively simple, quick and (sometimes unfortunately) rough. ## 7.5 Common Orders of Magnitude - O(1), a.k.a. CONSTANT: The number of operations is independent of the size of the problem. e.g., compute quadratic root. - O(log n), a.k.a. LOGARITHMIC. e.g., dictionary lookup, binary search. - O(n), a.k.a. LINEAR. e.g., sum up a list. - O(n log n), e.g., sorting. - O(n2), O(n3), O(nk), a.k.a. POLYNOMIAL. e.g., find closest pair of points. - O(2n), O(kn), a.k.a. EXPONENTIAL. e.g., Fibonacci, playing chess. ## 7.6 Best-Case, Average-Case and Worst-Case Analysis - For a given fixed size array, we might want to know: – The fewest number of operations (best case) that might occur. – The average number of operations (average case) that will occur. – The maximum number of operations (worst case) that can occur. - The last is the most common. The first is rarely used. - On the previous algorithm, the best case is O(1), but the average case and worst case are both O(n). ## 7.7 Approaching An Analysis Problem - Decide the important variable (or variables) that determine the “size” of the problem. For arrays and other “container classes” this will generally be the number of values stored. - Decide what to count. The order notation helps us here. - If each loop iteration does a fixed (or bounded) amount of work, then we only need to count the number of loop iterations. - We might also count specific operations. For example, in the previous exercise, we could count the number of comparisons. - Do the count and use order notation to describe the result. ## 7.8 Exercise: Order Notation For each version below, give an order notation estimate of the number of operations as a function of n: 1 ```cpp int count=0; for (int i=0; i ## 7.14 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.15 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? ## 7.16 Fibonacci Optimization: Order Notation of Time vs. Space The Fibonacci sequence is defined: ![alt text](fibonacci_sequence.png "fibonacci sequence") 1. Write a simple recursive version of Fibonacci that is a direct translation of the mathematical definition of the Fibonacci sequence. 2. Write an iterative version of Fibonacci that uses a vector to improve the running time of the function. 3. What is the order notation of the running time for each version? - Play this [animation](https://jidongxiao.github.io/CSCI1200-DataStructures/animations/recursion/fibonacci/index.html), which illustrates the time complexity of the recursvie version. 4. What is the order notation of the memory usage for each version?