CSC195, Class 23: Functions and Pointers Overview: * Detour: #include and #define directives. * Functions that return pointers. * Pointers to functions. + Contrast to Scheme. + Contrast to Java. * Lab. Notes: * Terminology: I tend to use function and procedure interchangably when discussing C. * Sorry about the (former) lack of outline yesterday. * Homework 5 ready. + Quick sort: Probabalistic divide and conquer Pick pivot Separate into smaller than pivot and greater than pivot Recurse on both sides O(2^n) BZZZ O(log_2(n)) BZZZ + Bubble sort BZZZ + Idunno Sort BZZZ + Insertion sort: Think of having two different sequences: unsorted and sorted Repeatedly put an element from unsorted into right place in sorted O(...) + Heap sort: Use a priority queue Build a heap (a funky priority queue) Take 'em out in order O(...) + Binary search BZZZ + Shell sort (not on HW, but good) + Merge sort: Merge Split lists into two halves Sort each half Merge 'em together O(...) + Selection sort: Repeatedly select shortest and shove at front O(...) + Bucket sort (not on HW, but good) "Pigeonhole principle" * Questions about the postconditions of permute? * Why doesn't C see future function declarations? ---------------------------------------- About Big O Notation * A key aspect of computer science. Observations: * When comparing algorithms, we use many different criteria? + Given eight different sorting algorithms, which one is best? (may depend on the orignal order of the original data) (may depend on the amount of data) (may depend on whether you like comparing) (may depend on whether you have a list or array) + Key criterion: Speed + Another criterion: Memory + Ease of implementation + Correctness/accuracy Given different algorithms, we need a way to compare their running time. * Mechanism one: Run 'em repeatedly. + Time consuming + Requires you to implement the algorithms + What holds at small input sizes doesn't always hold at large input sizes. + Algorithms can be remarkably inconsistent in running time. * We need a way to approximate the worst-case running time of an algorithm. Goals: + Don't worry about small differences between instructions. + Focus on the overall shape of the running time. The solution: Big O analysis f(n) is in O(g(n)) iff exist n0, d > 0, such that for all n > n0 |f(n)| < d*g(n) "for all n > n0" = "for big enough inputs" "d" = We don't care whether it's n or 2*n or 3*n or 1/10000*n When someone asks you for the Big-O of a function, they are, in essence, asking you to give a good upper bound on the running time. How do you compute big-O of something? * Iterative "built-in operation" : assume 1 step "conditional": assume 1 + cost of test + max(cost of branches) "repetition" count number of repetitions; count cost of body; multiply 'em "procedure call" : assume the cost of the procedure. Selection sort: for (k = 0; k < n; k++) i = index of smallest element in positions k .. n swap elements in positions i and k end for n repetitions c to swap n to find smallest O(n*(c+n)) = O(n^2 + cn) = O(n^2) n + n-1 + n-2 + ... 3 + 2 + 1 = n(n+1)/2 in O(n^2) Insertion sort: Repeatedly grab an unsorted element Find a place for it Put it in that place n repetitions log_n to find correct place n to insert O(n^2) * Recursive Write a recursive equation that gives the running time Solve the equation Merge sort: t(n) = Divide into two halves n sort each half t(n/2) for first half t(n/2) for second half merge two halves back together n t(1) = 1 t(n) = 2*n + 2*t(n/2) We need to solve this "recurrence relation" t(n) = 2*n + 2*t(n/2) = 2*n + 2*(2*n/2 + 2*t(n/4)) = 4*n + 4*t(n/4) = 4*n + 4*(2*n/4 + 2*t(n/8)) = 6*n + 8*t(n/8) = 3*2*n + 2^3*t(n/2^3) = 6*n + 8*(2*n/8 + 2*t(n/16)) = 8*n + 16*t(n/16) = 4*2*n + 2^4*t(n/2^4) = k*2*n + 2^k*t(n/2^k) Know t(1) = 1 Let n/(2^k) = 1, then t(n/(2^k)) = 1