Chapter 2
2.1 Computational Tractability
Algorithm Goals
Many algorithms are discrete: the goal is to efficiently find a solution that satisfies certain very clear conditions.
Running Time
Efficiency: amount of space (or memory)
Defining Efficiency
Consider scalability of algorithm: how does this effect efficiency?
Goal: concrete definition of efficiency that is platform-independent, instance-independent, and of predictive value when it comes to increasing input sizes
Worst-Case Running Times and Brute-Force Search
Worst-Case: bound on the largest possible running time the algorithm could have over all inputs of a given size N; see how this scales with N
Brute-Force: check all perfect matchings by enumeration to see if each is stable (slow & not intellectual)
Polynomial Time
When input size increases by a constant factor, the algorithm should only slow down by some constant factor C
In general, each problem has an intrinsic level of computational traceability (some with efficient solutions, others without)
Polynomial Running Time: When running time increases by lower-degree polynomials it exhibits better scaling behavior than when it increases by higher-degree polynomials (this is not always the best metric, but normally tends to be so)
2.2 Asymptotic Order of Growth
Running Time: Coarser Level of Granularity
Calculating precise bounds is exhausting and meaningless
Goal: identify broad classes of algorithms with similar behavior
Goal: express growth rate in a way that is insensitive to constant factors and low-order terms
O, Ω, and Θ
Asymptotic Upper Bound: T(n) is O(f(n)), if there exists constants c>0 and n0≥0 so that for all n≥n0, we have T(n)≤c*f(n) → T is asymptotically upper bounded by f.
Asymptotic Lower Bound: T(n) is Ω(f(n)), if there exists constants Ω>0 and n0≥0 so that for all n≥n0, we have T(n)≥ε*f(n) → T is asymptotically lower bounded by f.
Asymptotic Tight Bounds: if we can show that a running time T(n) is both O(f(n)) and also Ω(f(n)), then T(n) grows exactly like f(n) to within a constant factor.
Properties of Asymptotic Growth Rates
Asymptotic Bounds for Some Common Functions
Polynomials: Asymptotic rate of growth is determined by their “high-order term,” the one that determines the degree. Let f be a polynomial of degree d, in which the coefficient ad is positive. Then f=O(nd).
Logarithms: For every b>1 and everyx>0, we have logbn = O(nx)
Exponentials: For every r>1 and every d.0, we have nd = O(rn)
Asymptotically speaking, exponential functions are all different.
Logarithms grow more slowly than polynomials, and polynomials grow more slowly than exponentials.
Personal Thoughts
While this chapter dove into more of the complex details, it still was laid out in a way that was pretty easy to understand. 2.1 was definitely easier to understand than 2.2. Obviously there are a lot of notations that correspond to various functions, but I believe the overall concepts make sense. It would help to see applications of each of these bounds, which would also make this more interesting.
Readability: 7
Interesting: 5
2.3 Implementing the Stable Matching Algorithm Using Lists & Arrays
Arrays and Lists
Arrays:
Linked List:
For each element v, we need to maintain a pointer to the next element
The last element i's pointer is set to null
Pointer First points to the first element
Pointer Last points to the last element
Can allocate a record e for each element we want to include in the list: e.val and e.Next (also e.Prev, if doubly linked list)
Doubly Linked List:
Implementing the Stable Matching Algorithm
Each man is associated with a number from 0 to n, where n represents the total number of men. The same is done with the women.
2 arrays: for the men and women's preference lists
ManPref[m,i] & WomanPref[w,i]
Space needed for 2n individual: O(n2)
Required Constant Time Actions
Identify a free man
For a man m, identify the highest-ranked woman to whom he has not yet proposed
For a woman w, decide is she is currently engaged, and if so, identify her current partner
For a woman w and two men m and m', decide who is preferred by w
Identify a free man: use a linked list; take the first man on the list, delete him when engaged, potentially insert m' at the front of the list
Identify the highest-ranked woman on a list: have an array Next that indicates the position of the next woman on his list for each man; initialization: Next[m] = 1; search: ManPref[m,Next[m]], increment value of Next[m] after proposal
Identify Engaged Woman's Current Partner: have an array Current; Current[w] = man she is currently engaged to; initialization: Current[w] = null
Identify Woman's Preference between m and m': use a 2D array Ranking consisting of [w,m] for each woman and her preferences. Compare values of Ranking[w,m] and Ranking[w,m'] in constant time.
Total Implementation Runtime: O(n2)
Personal Thoughts
This section was relatively easy to understand as it was mostly review from CSCI 112. The refresher on lists and arrays was helpful, and was useful to review before exploring the implementation of the Stable Matching Problem. Between class lecture and this textbook section, the implementation and the associated running time makes sense.
Readability: 10
Interesting: 8
2.4 A Survey of Common Running Times
Two Kinds of Bounds:
Linear Time
Computing the Maximum
Merging Two Sorted Lists
Each element can be involved in at most O(n) comparisons → Running-Time Bound: O(n2)
Accounting Scheme: cost of each iteration at the time the element is selected and added to output list → Total Running Time: O(2n) → O(n)
O(n log n) Time
Common for any algorithm that splits input into two equal-sized parts, solves each part recursively, and then combines the two in linear time
Quadratic Time
Perform a search over all pairs of input items and spending constant time per pair: multiply the number of ways of choosing the first member of the pair by the number of ways of choosing the second member of the pair → n * n → O(n2)
Nested Loops: each loop with O(n) time results in O(n2) iterations
Cubic Time
Identify if there is disjoint (no elements in common) in some pair of sets (S1, S2,…Sn): for pair of sets Si and Sj, determine whether Si and Sj have an element in common.
O(nk) Time
Beyond Polynomial Time
Running times that grow faster than any polynomial: 2n and n!
Given a graph, find an independent set of maximum size
n!: number of ways to match up n items with n other items (number of perfect matches in the Stable Matching Problem)
n!: search space consists of all ways to arrange n items in order (Traveling Salesman Problem)
There are ways to solve n! algorithms in shorter times.
Sublinear Time
Running times are asymptotically smaller than linear → goal is to minimize the amount of querying necessary
Binary Search Algorithm: shrinking the size of the region where p might be by a factor of 2 each time/probe → after k probes, size is at most (1/2)kn
Personal Thoughts
This section was useful to read, as I sometimes struggle to identify situations where different running times apply. The more basic running times make sense, but I did struggle a little bit to understand the more complex running times (O(nk) time, sublinear time, etc.). This section will be good to look back upon when we get into later algorithms.
Readability: 6
Interesting: 6
2.5 A More Complex Data Structure: Priority Queues
The Problem
Priority Queue: Elements have a priority value (key), and each time we need to select an element from S, we want to take the one with highest priority.
Set of elements S
Smaller keys represent higher priority
Good for processing real-time events such as the scheduling of processes on a computer
Priority Queue Operations: any sequence of priority queue operations that results in the sorting of n numbers → O(nlogn)
A Data Structure for Implementing a Priority Queue
List: have a pointer to the min; adding new elements is easy, but extracting the minimum requires O(n) scan to find the new minimum
Sorted Array: binary search to find position of insertion + shifting all elements → O(nlogn)
Sorted Doubly Linked List: insertions are O(1), but O(n) to find position of insertion
The Heap: balanced binary tree; root + nodes with up to two children; key of any element is at least as large as the key of the parent node
Implementing the Heap Operations
Identifying Minimal Element: O(1)
Adding an Element: add element to the final position, then perform heapify-up recursively → O(logn)
Deleting an Element: move element w in position n to position i; key of element w may either be too small or too bit → O(logn)
* If key is too small: use heapify-up to reestablish order
* If key is too large: use heapify-down to sway the element with one of its children
Implementing Priority Queues with Heaps
StartHeap(N): returns an empty heap that is set up to store at most N elements → O(n)
Insert(H,v): inserts item v into heap H → O(logn)
FindMin(H): identifies the min → O(1)
Delete(H,i): deletes element in position i → O(logn)
ExtractMin(H): identifies and deletes minimum key element → O(logn)
Personal Thoughts
This section was pretty straightforward and easy to follow. I think going over the concepts in class before reading this section of the textbook was helpful in clarifying things that maybe would have been otherwise confusing. I appreciated the summary of the operation run times as these can sometimes be difficult to recall.
Readability: 9
Interesting: 6