# 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; 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 Exercise: A Slightly Harder Example Here’s an algorithm to determine if the value stored in variable x is also in an array called foo. Can you analyze it? What did you do about the if statement? What did you assume about where the value stored in x occurs in the array (if at all)? ```cpp int loc=0; bool found = false; while (!found && loc < n) { if (x == foo[loc]) found = true; else loc++; } if (found) cout << "It is there!\n"; ``` ## 7.7 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.8 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.9 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& 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?