1 / 83

CS 3343: Analysis of Algorithms

CS 3343: Analysis of Algorithms. Correctness Proof, Order of Growth, Asymptotic Notations. What is an algorithm?. Algorithms are the ideas behind computer programs. An algorithm is the thing that stays the same regardless of programming language and the computing hardware.

flynn
Download Presentation

CS 3343: Analysis of Algorithms

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. CS 3343: Analysis of Algorithms Correctness Proof, Order of Growth, Asymptotic Notations

  2. What is an algorithm? • Algorithms are the ideas behind computer programs. • An algorithm is the thing that stays the same regardless of programming language and the computing hardware

  3. What is an algorithm? (cont’) • An algorithm is a precise and unambiguous specification of a sequence of steps that can be carried out to solve a given problem or to achieve a given condition. • An algorithm accepts some value or set of values as input and produces a value or set of values as output. • Algorithms are closely intertwined with the nature of the data structure of the input and output values

  4. How to express algorithms? Nature language (e.g. English) Pseudocode Real programming languages Increasing precision Ease of expression Describe the ideasof an algorithm in nature language. Use pseudocode to clarify sufficiently tricky details of the algorithm.

  5. How to express algorithms? Nature language (e.g. English) Pseudocode Real programming languages Increasing precision Ease of expression To understand / describe an algorithm: Get the big idea first. Use pseudocode to clarify sufficiently tricky details

  6. Example: sorting • Input: A sequence of N numbers a1…an • Output: the permutation (reordering) of the input sequence such that a1≤ a2 … ≤ an. • Possible algorithms you’ve learned so far • Insertion, selection, bubble, quick, merge, … • More in this course • We seek algorithms that are both correctand efficient

  7. Insertion Sort InsertionSort(A, n) {for j = 2 to n { } } ▷ Pre condition: A[1..j-1] is sorted 1. Find position i in A[1..j-1] such that A[i] ≤ A[j] < A[i+1] 2. Insert A[j] between A[i] and A[i+1] ▷ Post condition: A[1..j] is sorted j 1 sorted

  8. j 1 i Key sorted Insertion Sort InsertionSort(A, n) {for j = 2 to n { key = A[j]; i = j - 1; while (i > 0) and (A[i] > key) { A[i+1] = A[i]; i = i – 1; } A[i+1] = key} }

  9. Correctness • What makes a sorting algorithm correct? • In the output sequence, the elements are ordered non-decreasingly • Each element in the input sequence has a unique appearance in the output sequence • [2 3 1] => [1 2 2] X • [2 2 3 1] => [1 1 2 3] X

  10. Correctness • For any algorithm, we must prove that it always returns the desired output for all legal instances of the problem. • For sorting, this means even if (1) the input is already sorted, or (2) it contains repeated elements. • Algorithm correctness is NOT obvious in some problems (e.g., optimization)

  11. How to prove correctness? • Given a concrete input, eg. <4,2,6,1,7>trace it and prove that it works. • Given an abstract input, eg. <a1, … an> trace it and prove that it works. • Sometimes it is easier to find a counterexample to show that an algorithm does NOT work. • Think about all small examples • Think about examples with extremes of big and small • Think about examples with ties • Failure to find a counterexample does NOT mean that the algorithm is correct

  12. j 1 i Key sorted An Example: Insertion Sort InsertionSort(A, n) {for j = 2 to n { key = A[j]; i = j - 1;▷Insert A[j] into the sorted sequence A[1..j-1] while (i > 0) and (A[i] > key) { A[i+1] = A[i]; i = i – 1; } A[i+1] = key} }

  13. Example of insertion sort Done!

  14. Use loop invariants to prove the correctness of loops • A loop invariant (LI) is a formal statement about the variables in your program which holds true throughout the loop • Claim: at the start of each iteration of the for loop, the subarray A[1..j-1] consists of the elements originally in A[1..j-1] but in sorted order. • Proof by induction • Initialization: the LI is true prior to the 1st iteration • Maintenance: if the LI is true before the jth iteration, it remains true before the (j+1)th iteration • Termination: when the loop terminates, the LI gives us a useful property to show that the algorithm is correct

  15. Loop invariants and correctness of insertion sort • Claim: at the start of each iteration of the for loop, the subarray consists of the elements originally in A[1..j-1] but in sorted order. • Proof: by induction

  16. Prove correctness using loop invariants InsertionSort(A, n) {for j = 2 to n { key = A[j]; i = j - 1;▷Insert A[j] into the sorted sequence A[1..j-1] while (i > 0) and (A[i] > key) { A[i+1] = A[i]; i = i – 1; } A[i+1] = key} } Loop invariant: at the start of each iteration of the for loop, the subarray A[1..j-1] consists of the elements originally in A[1..j-1] but in sorted order.

  17. Initialization InsertionSort(A, n) {for j = 2 to n { key = A[j]; i = j - 1;▷Insert A[j] into the sorted sequence A[1..j-1] while (i > 0) and (A[i] > key) { A[i+1] = A[i]; i = i – 1; } A[i+1] = key} } Subarray A[1] is sorted. So loop invariant is true before the loop starts. Loop invariant: at the start of each iteration of the for loop, the subarray A[1..j-1] consists of the elements originally in A[1..j-1] but in sorted order.

  18. Loop variant will be true before iteration j +1 j 1 i Key sorted Loop invariant: at the start of each iteration of the for loop, the subarray A[1..j-1] consists of the elements originally in A[1..j-1] but in sorted order. Maintenance InsertionSort(A, n) {for j = 2 to n { key = A[j]; i = j - 1;▷Insert A[j] into the sorted sequence A[1..j-1] while (i > 0) and (A[i] > key) { A[i+1] = A[i]; i = i – 1; } A[i+1] = key} } Assume loop variant is true prior to iteration j

  19. Loop invariant: at the start of each iteration of the for loop, the subarray A[1..j-1] consists of the elements originally in A[1..j-1] but in sorted order. Termination InsertionSort(A, n) {for j = 2 to n { key = A[j]; i = j - 1;▷Insert A[j] into the sorted sequence A[1..j-1] while (i > 0) and (A[i] > key) { A[i+1] = A[i]; i = i – 1; } A[i+1] = key} } The algorithm is correct! Upon termination, A[1..n] contains all the original elements of A in sorted order. n j=n+1 1 Sorted

  20. Efficiency • Correctness alone is not sufficient • Brute-force algorithms exist for most problems • To sort n numbers, we can enumerate all permutations of these numbers and test which permutation has the correct order • Why cannot we do this? • Too slow! • By what standard?

  21. How to measure complexity? • Accurate running time is not a good measure • It depends on input • It depends on the machine you used and who implemented the algorithm • It depends on the weather, maybe  • We would like to have an analysis that does not depend on those factors

  22. Machine-independent • A generic uniprocessor random-access machine (RAM) model • No concurrent operations • Each simple operation (e.g. +, -, =, *, if, for) takes 1 step. • Loops and subroutine calls are notsimple operations. • All memory equally expensive to access • Constant word size • Unless we are explicitly manipulating bits

  23. Running Time • Number of primitive steps that are executed • Except for time of executing a function call most statements roughly require the same amount of time • y = m * x + b • c = 5 / 9 * (t - 32 ) • z = f(x) + g(x) • We can be more exact if need be

  24. Running time of insertion sort • The running time depends on the input: an already sorted sequence is easier to sort. • Parameterize the running time by the size of the input, since short sequences are easier to sort than long ones. • Generally, we seek upper bounds on the running time, because everybody likes a guarantee.

  25. Kinds of analyses • Worst case • Provides an upper bound on running time • An absolute guarantee • Best case – not very useful • Average case • Provides the expected running time • Very useful, but treat with care: what is “average”? • Random (equally likely) inputs • Real-life inputs

  26. Example: analysis of insertion Sort InsertionSort(A, n) {for j = 2 to n { key = A[j] i = j - 1; while (i > 0) and (A[i] > key) { A[i+1] = A[i] i = i - 1 } A[i+1] = key} } How many times will this line execute?

  27. Example: analysis of insertion Sort InsertionSort(A, n) {for j = 2 to n { key = A[j] i = j - 1; while (i > 0) and (A[i] > key) { A[i+1] = A[i] i = i - 1 } A[i+1] = key} } How many times will this line execute?

  28. Analysis of insertion Sort: exact Statement cost time__ InsertionSort(A, n) { for j = 2 to n { c1 n key = A[j] c2 (n-1) i = j - 1; c3 (n-1) while (i > 0) and (A[i] > key) { c4 S A[i+1] = A[i] c5 (S-(n-1)) i = i - 1 c6 (S-(n-1)) } 0 A[i+1] = key c7 (n-1) } 0 } S = t2 + t3 + … + tn where tj is number of while expression evaluations for the jth for loop iteration

  29. Analyzing Insertion Sort : exact • T(n)=c1n + c2(n-1) + c3(n-1) + c4S + c5(S - (n-1)) + c6(S - (n-1)) + c7(n-1) = c8S + c9n + c10 • What can S be? • Best case -- inner loop body never executed • tj = 1  S = n - 1 • T(n) = an + b is a linear function • Worst case -- inner loop body executed for all previous elements • tj = j  S = 2 + 3 + … + n = n(n+1)/2 - 1 • T(n) = an2 + bn + c is a quadratic function • Average case • Can assume that on average, we have to insert A[j] into the middle of A[1..j-1], so tj = j/2 • S ≈ n(n+1)/4 • T(n) is still a quadratic function Θ (n) Θ (n2) Θ (n2)

  30. Asymptotic Analysis • Running time depends on the size of the input • Larger array takes more time to sort • T(n): the time taken on input with size n • Look at growth of T(n) as n→∞. “Asymptotic Analysis” • Size of input is generally defined as the number of input elements • In some cases may be tricky

  31. Asymptotic Analysis • Ignore actual and abstract statement costs • Order of growth is the interesting measure: • Highest-order term is what counts • As the input size grows larger it is the high order term that dominates

  32. Comparison of functions For a super computer that does 1 trillion operations per second, it will be longer than 1 billion years

  33. Order of growth 1 << log2n << n << nlog2n << n2 << n3 << 2n << n! (We are slightly abusing of the “<<“ sign. It means a smaller order of growth).

  34. Asymptotic notations • We say InsertionSort’s worst-case running time is Θ(n2) • Properly we should say running time is inΘ(n2) • It is also in O(n2 ) • What’s the relationship between Θ and O? • Formal definition soon

  35. Analysis of insertion Sort: Asymptotic Statement cost time__ InsertionSort(A, n) { for j = 2 to n { c1 n key = A[j] c2 (n-1) i = j - 1; c3 (n-1) while (i > 0) and (A[i] > key) { c4 S A[i+1] = A[i] c5 (S-(n-1)) i = i - 1 c6 (S-(n-1)) } 0 A[i+1] = key c7 (n-1) } 0 } What are the basic operations (most executed lines)?

  36. Analysis of insertion Sort: Asymptotic Statement cost time__ InsertionSort(A, n) { for j = 2 to n { c1 n key = A[j] c2 (n-1) i = j - 1; c3 (n-1) while (i > 0) and (A[i] > key) { c4 S A[i+1] = A[i] c5 (S-(n-1)) i = i - 1 c6 (S-(n-1)) } 0 A[i+1] = key c7 (n-1) } 0 }

  37. Analysis of insertion Sort: Asymptotic Statement cost time__ InsertionSort(A, n) { for j = 2 to n { c1 n key = A[j] c2 (n-1) i = j - 1; c3 (n-1) while (i > 0) and (A[i] > key) { c4 S A[i+1] = A[i] c5 (S-(n-1)) i = i - 1 c6 (S-(n-1)) } 0 A[i+1] = key c7 (n-1) } 0 }

  38. What can S be? • S = j=2..n tj • Best case: • Worst case: • Average case: Inner loop stops when A[i] <= key, or i = 0 i j 1 Key sorted

  39. Best case • Array already sorted • S = j=2..n tj • tj = 1 for all j • S = n-1 T(n) = Θ (n) Inner loop stops when A[i] <= key, or i = 0 i j 1 Key sorted

  40. Worst case • Array originally in reversely sorted order • S = j=2..ntj • tj = j • S = j=2..n j = 2 + 3 + … + n = (n-1) (n+2) / 2 = Θ (n2) Inner loop stops when A[i] <= key i j 1 Key sorted

  41. Average case • Array in random order • S = j=2..n tj • tj = j / 2 on average • S = j=2..n j/2 = ½ j=2..n j = (n-1) (n+2) / 4 = Θ (n2) Inner loop stops when A[i] <= key i j 1 Key sorted What if we use binary search? Answer: still Θ(n2)

  42. Exact analysis is hard and unnecessary! • Worst-case and average-case are difficult to deal with precisely, because the details can be very complicated It may be easier to talk about upper and lower bounds of the function.

  43. Asymptotic notations • O: Big-Oh • Ω: Big-Omega • Θ: Theta • o: Small-oh • ω: Small-omega

  44. Big O • Informally, O (g(n)) is the set of all functions with a smaller or same order of growth as g(n), within a constant multiple • If we say f(n) is in O(g(n)), it means that g(n) is an asymptotic upper bound of f(n) • Intuitively, it is like f(n) ≤ g(n) • What is O(n2)? • The set of all functions that grow slower than or in the same order as n2 • Abuse of notation (for convenience): f(n) = O(g(n)) actually means f(n)  O(g(n))

  45. So: n  O(n2) n2 O(n2) 1000n  O(n2) n2 + n  O(n2) 100n2 + n  O(n2) But: 1/1000 n3 O(n2) Intuitively, O is like ≤

  46. (optional) small o • Informally, o (g(n)) is the set of all functions with a strictly smaller growth as g(n), within a constant multiple • What is o(n2)? • The set of all functions that grow slower than n2 So: 1000n  o(n2) But: n2 o(n2) Intuitively, o is like <

  47. Big Ω • Informally, Ω (g(n)) is the set of all functions with a larger or same order of growth as g(n), within a constant multiple • f(n)  Ω(g(n)) means g(n) is an asymptotic lower bound of f(n) • Intuitively, it is like g(n) ≤ f(n) So: n2 Ω(n) 1/1000 n2 Ω(n) But: 1000 n Ω(n2) Intuitively, Ω is like ≥ • Abuse of notation (for convenience): f(n) =Ω(g(n)) actually means f(n) Ω(g(n))

  48. (optional) small ω • Informally, ω (g(n)) is the set of all functions with a larger order of growth as g(n), within a constant multiple So: n2 ω(n) 1/1000 n2 ω(n) n2 ω(n2) Intuitively, ω is like >

  49. Theta (Θ) • Informally, Θ (g(n)) is the set of all functions with the same order of growth as g(n), within a constant multiple • f(n)  Θ(g(n)) means g(n) is an asymptotically tight bound of f(n) • Intuitively, it is like f(n) = g(n) • What is Θ(n2)? • The set of all functions that grow in the same order as n2 • Abuse of notation (for convenience): f(n) =Θ(g(n)) actually means f(n) Θ(g(n)) Θ(1) means constant time.

  50. So: n2 Θ(n2) n2 + n  Θ(n2) 100n2 + n  Θ(n2) 100n2 + log2n  Θ(n2) But: nlog2n  Θ(n2) 1000n  Θ(n2) 1/1000 n3Θ(n2) Intuitively, Θ is like =

More Related