3 · Greedy algorithms
DPV Ch 5 + lectures 21–26 + HW4 P2–P5. Two exam problems come from here. Expect a hand trace (Kruskal / Prim / union-find / Huffman), a true/false-with-proof item like DPV 5.9, or "prove it or give a counterexample".
Router
| If the question says… | Do this |
|---|---|
| "Run Kruskal / in what order are edges added / give a cut that justifies each" | Sort edges (ties alphabetical). For each added edge: cut = the current component of one endpoint vs everything else. Skipped edges: say "would form a cycle". |
| "Run Prim, show the cost array" | Table: rows = vertices, one column per removal. cost(z) = min(cost(z), w(v,z)), not dist + w. Ties alphabetical. |
| "State of the disjoint-sets structure" | Track parent π and rank. Union by rank; tie rule as stated. Path compression only changes nodes on a find path. |
| "Huffman encoding / length of encoded file" | Repeatedly merge the two smallest. Cost = Σ f·depth = sum of all internal-node frequencies. |
| "Prove or give a counterexample" (MST statements) | True → cut property or cycle swap \(T - e' + e\). False → triangle or 4-vertex graph with numbers, and state both trees' costs. |
| "Show the greedy algorithm may fail" | Smallest instance where greedy's first choice blocks two better choices. Give greedy's output, the optimum, and both values. |
| "Prove the greedy algorithm is optimal" | Exchange argument (recipe below) + induction. |
| "Is edge e in an MST? O(m+n)" | HW4 P2: DFS from u using only edges lighter than w(e). Reach v → no. Otherwise yes. |
| "Horn formula, find a satisfying assignment" | All false; fire unsatisfied implications; then check pure negative clauses. |
| "Set cover greedy bound" | \(n_{t+1} \le n_t(1 - 1/k)\), so \(n_t < n e^{-t/k}\); at \(t = k\ln n\), \(n_t < 1\). |
1 · The greedy template and how to prove it
sort candidates by some rule
S = ∅
for each candidate c, in that order:
if S ∪ {c} is feasible: S = S ∪ {c}
return S
Greedy is correct only when both hold:
- Greedy-choice property: some optimal solution contains the greedy first choice.
- Optimal substructure: after the greedy choice, what is left is a smaller instance of the same problem, and an optimal answer to it extends to an optimal answer overall.
DP also needs optimal substructure. The greedy-choice property is what lets greedy commit to one choice instead of trying all of them.
- Let \(g\) be the greedy algorithm's first choice. Let \(O\) be any optimal solution. If \(g \in O\), done with this step.
- Otherwise pick the element \(o \in O\) that ______ (the one greedy "would have beaten": the first-finishing activity, the lowest-value item used, the heavier cycle edge…).
- Form \(O' = O - \{o\} \cup \{g\}\).
- Feasible because ______ (\(g\) finishes no later than \(o\); same total weight; still a spanning tree…).
- Not worse because ______ (\(|O'| = |O|\); benefit changes by \(y(v_g - v_o) \ge 0\); weight changes by \(w(g) - w(o) \le 0\)).
- So \(O'\) is optimal and contains \(g\). The rest of \(O'\) is an optimal solution to the smaller subproblem left after \(g\). By induction greedy solves that subproblem optimally, so greedy is optimal.
2 · Fractional knapsack
Do: compute value \(v_i = b_i / w_i\), sort decreasing, take whole items until the next one does not fit, then take a fraction of it. \(O(n \log n)\).
Lecture example, \(W = 10\):
| Item | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| Weight | 4 | 8 | 2 | 6 | 1 |
| Benefit | 12 | 32 | 40 | 30 | 50 |
| Value b/w | 3 | 4 | 20 | 5 | 50 |
- By value: item 5 (1 g, 50), item 3 (2 g, 40), item 4 (6 g, 30), then 1 g of item 2 (4). Total \(50 + 40 + 30 + 4 = \mathbf{124}\), optimal.
- Wrong rule, increasing weight: 5, 3, 1, half of 4: \(50 + 40 + 12 + 15 = 117\).
- Wrong rule, decreasing benefit: 5, 3, then 7/8 of item 2: \(50 + 40 + 28 = 118\).
Proof (exchange, by contradiction). Let \(x_i\) be the amount of item \(i\) taken. Suppose some solution beats greedy. Then there are items \(i, j\) with \(v_i > v_j\), \(x_j > 0\), and \(x_i < w_i\): it uses some of a lower-value item while a higher-value item is not used up. Let \(y = \min\{w_i - x_i,\ x_j\}\). Replace \(y\) grams of \(j\) by \(y\) grams of \(i\). Weight is unchanged and benefit rises by \(y(v_i - v_j) > 0\), so that solution was not optimal. Contradiction.
0/1 knapsack: greedy by value fails
- Lecture (\(W = 9\); items (w, b) = (4,20), (2,3), (2,6), (6,25), (2,80)): greedy by value takes 5, then 1 (weight 6, benefit 100); item 4 does not fit, so greedy stops at 100. Optimum is items 1, 3, 5 = 106. (DP table on page 4.)
- Smallest version: \(W = 4\), items (3, 5), (2, 3), (2, 3). Greedy by value takes (3, 5) and nothing else fits: 5. The two small items give 6.
- Deck: \(W = 50\), gold (10, $60), silver (20, $100), bronze (30, $120). Fractional greedy = $240. 0/1 greedy = gold + silver = $160. 0/1 optimum = silver + bronze = $220.
Why the proof breaks: the exchange swaps weight unit-for-unit. In 0/1 you cannot take \(y\) grams of an item, so the swap is not available. 0/1 knapsack needs DP in \(O(nW)\).
3 · Activity selection (interval scheduling)
Do: sort by finish time; take an activity if it starts at or after the last chosen finish. \(O(n \log n)\).
ActivitySelect(s, f):
sort activities so f1 ≤ f2 ≤ … ≤ fn
S = {a1}; last = f1
for i = 2 to n:
if si ≥ last: S = S ∪ {ai}; last = fi
return S
Deck example: A 8–21, B 9–13, C 12–14, D 13–17, E 16–18, F 17–21, G 21–22. By finish: B (13), C (14, starts 12 < 13, skip), D (17, starts 13 ✓), E (18, starts 16 < 17, skip), A (21, skip), F (21, starts 17 ✓), G (22, starts 21 ✓). Result {B, D, F, G}, 4 activities. Brute force confirms 4 is the maximum.
Proof sketch. Let \(g_1\) be the activity that finishes first and \(O = \{o_1, \dots, o_k\}\) an optimal schedule sorted by finish time. Swap: \(O' = O - \{o_1\} \cup \{g_1\}\). Every \(o_j\) with \(j \ge 2\) starts at or after \(f(o_1) \ge f(g_1)\), so \(O'\) is feasible, with \(k\) activities, so it is optimal. What remains is the same problem on \(\{a : s(a) \ge f(g_1)\}\), which contains \(O' - \{g_1\}\) (size \(k-1\)). By induction greedy picks \(k - 1\) there. Total \(k\).
Rules that fail (each counterexample checked):
- Earliest start: [0,10], [1,2], [3,4]. Greedy takes [0,10] only (1); optimum 2.
- Shortest duration: [1,5], [4,7], [6,10]. Greedy takes [4,7], which overlaps both others (1); optimum [1,5], [6,10] (2).
- Weighted version: earliest finish ignores weights. Use DP (weighted interval scheduling, page 4).
4 · Minimum spanning trees
Facts used in every proof: a tree on \(n\) vertices has \(n - 1\) edges; any connected graph with \(n - 1\) edges is a tree; removing a tree edge splits the tree into two components; adding a non-tree edge creates exactly one cycle.
Cut property
Proof (lecture). \(X \subseteq T\) for some MST \(T\). If \(e \in T\), done. Otherwise add \(e\) to \(T\): this creates a cycle. The cycle crosses the cut at \(e\), so it must cross back at some other edge \(e'\). Let \(T' = T \cup \{e\} - \{e'\}\). \(T'\) is connected with \(n - 1\) edges, so it is a tree. \(e' \notin X\) because no edge of \(X\) crosses the cut, so \(X \cup \{e\} \subseteq T'\). And $$\text{weight}(T') = \text{weight}(T) + w(e) - w(e') \le \text{weight}(T)$$ since \(e\) is a lightest crossing edge. So \(T'\) is also an MST.
With distinct weights, the lightest edge across any cut is in every MST (the swap would give a strictly lighter tree).
Kruskal + union-find
Do: sort edges; add the next lightest edge that does not form a cycle. Each added edge joins two components, so it is the lightest edge across the cut (one of those components, rest of graph). That is why it is safe.
procedure kruskal(G, w)
for all u ∈ V: makeset(u)
X = {}
sort the edges E by weight
for all edges {u, v} ∈ E, in increasing order of weight:
if find(u) ≠ find(v):
add edge {u, v} to X
union(u, v)
procedure makeset(x)
π(x) = x
rank(x) = 0
function find(x) (with path compression)
if x ≠ π(x): π(x) = find(π(x))
return π(x)
procedure union(x, y) (union by rank)
rx = find(x); ry = find(y)
if rx = ry: return
if rank(rx) > rank(ry): π(ry) = rx
else:
π(rx) = ry
if rank(rx) = rank(ry): rank(ry) = rank(ry) + 1
- Union by rank: \(\text{rank}(x) < \text{rank}(\pi(x))\); a root of rank \(k\) has at least \(2^k\) nodes; so trees have height \(\le \log n\) and find is \(O(\log n)\).
- Path compression: every node on the find path is re-pointed straight at the root. Ranks are not changed. Amortized cost per operation becomes \(O(\log^* n)\).
- Kruskal running time: sort \(O(m \log m) = O(m \log n)\) (since \(m \le n^2\)), plus \(2m\) finds and \(n - 1\) unions at \(O(\log n)\) each. Total \(O(m \log n)\).
Prim
Do: grow one tree \(S\). Each step adds the vertex outside \(S\) with the smallest \(\text{cost}(v) = \min_{u \in S} w(u, v)\). It is Dijkstra with a different key, so it has the same running time, \(O((n + m)\log n)\) with a binary heap.
procedure prim(G, w)
for all u ∈ V:
cost(u) = ∞
prev(u) = nil
pick any initial node u0
cost(u0) = 0
H = makequeue(V) (priorities are cost values)
while H is not empty:
v = deletemin(H)
for each {v, z} ∈ E:
if z ∈ H and cost(z) > w(v, z):
cost(z) = w(v, z)
prev(z) = v
decreasekey(H, z)
Trap: Dijkstra uses dist(v) + w(v, z). Prim uses w(v, z) alone. Prim is correct with negative edges (it only relies on the cut property); Dijkstra is not.
Worked: DPV 5.1
(a) MST cost = 19. (b) 2 MSTs: {AE, EF, FG, GH, CG, DG} plus either BE or BF (both weight 2). (c) Kruskal order (ties alphabetical) with a justifying cut. The cut is the current component of one endpoint vs the rest; the added edge is a lightest edge crossing it.
| # | Edge | Cut \(S\) (vs \(V - S\)) | Edges crossing the cut |
|---|---|---|---|
| 1 | AE (1) | {A} | AB 6, AE 1 |
| 2 | EF (1) | {A, E} | AB 6, BE 2, EF 1 |
| 3 | BE (2) | {A, E, F} | AB 6, BE 2, BF 2, CF 5, FG 3 (tie with BF: the source of the 2nd MST) |
| BF (2) | skip: B and F are already connected (B–E–F), would form a cycle | ||
| 4 | FG (3) | {A, B, E, F} | BC 5, CF 5, FG 3 |
| 5 | GH (3) | {H} | DH 7, GH 3 |
| 6 | CG (4) | {C} | BC 5, CD 6, CF 5, CG 4 |
| BC (5), CF (5) | skip: cycle | ||
| 7 | DG (5) | {D} | CD 6, DG 5, DH 7 |
| AB 6, CD 6, DH 7 | skip: cycle (tree already has 7 = n − 1 edges) | ||
Total \(1 + 1 + 2 + 3 + 3 + 4 + 5 = 19\). (The other endpoint's component also works as a cut every time; a script checked both.)
Worked: DPV 5.2
(a) Prim from A, alphabetical ties. Removal order A, B, C, G, D, F, H, E. Cost array after each removal:
| vertex | init | +A | +B | +C | +G | +D | +F | +H | +E |
|---|---|---|---|---|---|---|---|---|---|
| A | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| B | ∞ | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| C | ∞ | ∞ | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
| D | ∞ | ∞ | ∞ | 3 | 1 | 1 | 1 | 1 | 1 |
| E | ∞ | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 |
| F | ∞ | 8 | 6 | 6 | 1 | 1 | 1 | 1 | 1 |
| G | ∞ | ∞ | 6 | 2 | 2 | 2 | 2 | 2 | 2 |
| H | ∞ | ∞ | ∞ | ∞ | 1 | 1 | 1 | 1 | 1 |
Edges added: AB 1, BC 2, CG 2, GD 1, GF 1, GH 1, AE 4. Cost 12. (After +G, D, F, H tie at 1; alphabetical picks D, then F, then H.)
(b) Kruskal. Order AB (1), DG (1), FG (1), GH (1), BC (2), CG (2), AE (4); cost 12; this MST is unique. Disjoint-sets states with union by rank and path compression. DPV does not fix a tie rule for 5.2; below uses "alphabetically earlier root points to the later one" (same rule as 5.11). State your rule on the exam.
| Step | Edge | Parent pointers (non-roots) | Ranks > 0 |
|---|---|---|---|
| 1 | AB add | A→B | B 1 |
| 2 | DG add | A→B, D→G | B 1, G 1 |
| 3 | FG add | A→B, D→G, F→G | B 1, G 1 |
| 4 | GH add | A→B, D→G, F→G, H→G | B 1, G 1 |
| 5 | BC add | A→B, C→B, D→G, F→G, H→G | B 1, G 1 |
| 6 | CG add (roots B, G tie) | A→B, C→B, B→G, D→G, F→G, H→G | B 1, G 2 |
| 7 | CD skip; find(C) compresses C→G | A→B, B→G, C→G, D→G, F→G, H→G | B 1, G 2 |
| 8 | AE add; find(A) compresses A→G, then E→G | everything → G | B 1, G 2 |
| – | DH, EF, BF, BG, AF skip | no change |
Union-find exercise: DPV 5.11
Singletons {1}, …, {8}; union by rank, path compression; ties: lower-numbered root points to the higher. Computed by script:
| Operation | What happens | Non-root pointers after |
|---|---|---|
| union(1,2) | tie (0,0): 1→2, rank(2)=1 | 1→2 |
| union(3,4) | 3→4, rank(4)=1 | 1→2, 3→4 |
| union(5,6) | 5→6, rank(6)=1 | + 5→6 |
| union(7,8) | 7→8, rank(8)=1 | + 7→8 |
| union(1,4) | roots 2, 4, tie (1,1): 2→4, rank(4)=2 | 1→2, 2→4, 3→4, 5→6, 7→8 |
| union(6,7) | roots 6, 8, tie (1,1): 6→8, rank(8)=2 | 1→2, 2→4, 3→4, 5→6, 6→8, 7→8 |
| union(4,5) | find(5): 5→6→8, compress 5→8. Roots 4, 8, tie (2,2): 4→8, rank(8)=3 | 1→2, 2→4, 3→4, 4→8, 5→8, 6→8, 7→8 |
| find(1) | path 1→2→4→8; compress 1→8, 2→8 | 1→8, 2→8, 3→4, 4→8, 5→8, 6→8, 7→8 |
Final: one tree rooted at 8 (rank 3). Children of 8: 1, 2, 4, 5, 6, 7. Node 3 hangs under 4. Ranks: rank(8)=3, rank(4)=2, rank(2)=1, rank(6)=1, others 0.
DPV 5.9: true or false (with proof or counterexample)
Graph undirected; weights not assumed distinct unless stated. Every counterexample below was checked by enumerating all spanning trees.
| Verdict | One-line reason | |
|---|---|---|
| (a) more than |V|−1 edges and a unique heaviest edge ⇒ that edge is in no MST | False | Triangle AB 1, BC 2, AC 3 plus pendant edge AD 10. It has 4 > 3 edges, AD is the unique heaviest, and AD is in every MST (only way to reach D). |
| (b) a cycle with a unique heaviest edge e ⇒ e in no MST | True | If \(e \in T\), remove it; the rest of the cycle reconnects the two sides through some edge \(e'\) with \(w(e') < w(e)\). \(T - e + e'\) is lighter. Contradiction. |
| (c) any minimum-weight edge e is in some MST | True | Cut property with \(X = \emptyset\), \(S = \{u\}\) where \(e = \{u, v\}\): \(e\) is a lightest edge across that cut. |
| (d) a unique lightest edge is in every MST | True | If MST \(T\) lacks \(e\), \(T + e\) has a cycle; any other cycle edge \(e'\) is strictly heavier; \(T + e - e'\) is lighter. Contradiction. |
| (e) e in some MST ⇒ e is a lightest edge across some cut | True | Remove \(e\) from that MST \(T\); the two pieces form a cut. If a crossing edge \(e'\) were lighter, \(T - e + e'\) would be lighter than \(T\). |
| (f) a cycle with a unique lightest edge e ⇒ e in every MST | False | AB 2, BC 5, AC 5, AD 1, BD 1. Cycle A–B–C has unique lightest AB, but every MST is AD, BD + one 5-edge (cost 7); AB is the unique heaviest on cycle A–B–D, so it is in no MST. |
| (g) Dijkstra's shortest-path tree is an MST | False | Triangle SA 2, SB 2, AB 1. Tree from S = {SA, SB}, cost 4. MST = AB + one S-edge, cost 3. |
| (h) a shortest path between two nodes is part of some MST | False | Triangle SA 3, SB 2, AB 2. Shortest S–A path is the edge SA (3 < 4). Unique MST = {SB, AB}, cost 4, without SA. |
| (i) Prim works with negative edges | True | Prim's correctness is the cut property, which never uses the sign of weights. (Also: adding a constant \(c\) to all weights adds \((n-1)c\) to every spanning tree, so the MST does not change.) |
| (j) an r-path s→t in G ⇒ every MST has an r-path s→t | True | If the MST's s–t path has an edge \(e\) with \(w(e) \ge r\), remove it to get a cut with s, t on opposite sides. The r-path in G crosses it on some \(e'\) with \(w(e') < r \le w(e)\). \(T - e + e'\) is lighter. Contradiction. |
HW4 P2: is edge e in the MST? O(m + n)
Distinct weights, so the MST is unique. Claim: \(e = \{u, v\}\) is in the MST iff there is no u–v path that uses only edges lighter than \(w(e)\).
procedure inMST(G, w, e = {u, v})
for all x ∈ V: visited(x) = false
explore(u), but only follow edges {x, y} with w(x, y) < w(e)
if visited(v): return "e is not in the MST"
else: return "e is in the MST"
- If v is reached: that path plus \(e\) is a cycle on which \(e\) is the unique heaviest edge. By 5.9(b), \(e\) is in no MST.
- If v is not reached: let \(S\) = vertices reached from u. Every edge leaving \(S\) has weight \(\ge w(e)\) (otherwise its other end would have been reached), and \(e\) leaves \(S\). With distinct weights, \(e\) is the unique lightest edge across \((S, V - S)\), so by the cut property it is in the MST.
- Time: one DFS/BFS, each edge checked once: \(O(m + n)\).
HW4 P3: the divide-and-conquer MST algorithm is wrong
The algorithm outputs the lightest edge between \(V_1\) and \(V_2\), then recurses on each half. It can only ever output one edge across each split, and it is forced to use edges inside each half.
| Split \(V_1 \mid V_2\) | Output | Cost |
|---|---|---|
| {A, B} | {C, D} | AC (lightest crossing), then AB, CD | 9 |
| {A, C} | {B, D} | AB, then AC, BD | 8 |
| {A, D} | {B, C} | AB, then AD, BC | 8 |
True MST = {AB, AC, AD}, cost 6. Every balanced split gives a wrong answer, because the star needs two edges inside no half. The algorithm is incorrect. (Checked by script.)
5 · Huffman encoding
Do: put all symbols in a min-heap by frequency; repeatedly remove the two smallest, merge them under a new node whose frequency is their sum; insert it back. \(n - 1\) merges, \(O(n \log n)\).
procedure Huffman(f)
Input: array f[1..n] of frequencies
Output: an encoding tree with n leaves
let H be a priority queue of integers, ordered by f
for i = 1 to n: insert(H, i)
for k = n + 1 to 2n − 1:
i = deletemin(H), j = deletemin(H)
create a node numbered k with children i, j
f[k] = f[i] + f[j]
insert(H, k)
Each internal node's frequency is the sum of the leaves under it, so it counts each leaf once per edge above it. (Lecture phrasing: sum of all nodes except the root. Same number.)
Why greedy works: the two least frequent symbols can be placed as siblings at the deepest level of some optimal tree (swap them down; cost does not increase). Merging them gives a smaller instance of the same problem.
Worked: lecture example (130 million symbols)
A 70, B 3, C 20, D 37 (millions). Merges: B + C = 23; 23 + D = 60; 60 + A = 130. Depths: A 1, D 2, B 3, C 3.
Cost \(= 70\cdot1 + 37\cdot2 + 3\cdot3 + 20\cdot3 = 213\) = internal nodes \(23 + 60 + 130 = 213\) megabits, vs 260 for a fixed 2-bit code.
DPV 5.13 and 5.14 (from answers.txt)
| Exercise | Merges | Code | Cost |
|---|---|---|---|
| 5.13: A 31%, C 20%, G 9%, T 40% | G+C = 29; 29+A = 60; T+60 = 100 | T = 0, A = 11, C = 101, G = 100 | \(29 + 60 + 100 = 189\) bits per 100 chars = 1.89 bits/char |
| 5.14: a 1/2, b 1/4, c 1/8, d 1/16, e 1/16 | d+e = 1/8; c+(de) = 1/4; b+… = 1/2; a+… = 1 | a = 0, b = 10, c = 110, d = 1110, e = 1111 | 1.875 bits/char; 1,000,000 chars → 1,875,000 bits |
Which child gets 0 or 1 is arbitrary; codeword lengths are what is graded.
DPV 5.15: which codes can Huffman produce?
- (a) {0, 10, 11}: possible. Example \((f_a, f_b, f_c) = (0.5, 0.3, 0.2)\): merge b, c first, then a joins at the root. Any frequencies where a is the largest work.
- (b) {0, 1, 00}: impossible. 0 is a prefix of 00, and Huffman codes are always prefix-free (symbols are leaves).
- (c) {10, 01, 00}: impossible. The tree is not full: node "1" has only one child (10). Every Huffman merge creates a node with two children. (Also, replacing 10 by 1 is strictly shorter, so this code is never optimal.)
DPV 5.16 (and HW4 P5): length-1 codewords
Setup used in both proofs. With \(n \ge 3\) symbols, just before the second-to-last merge exactly three nodes remain, \(P \le Q \le R\), with \(P + Q + R = 1\). Huffman merges \(P\) and \(Q\), then merges \(P + Q\) with \(R\). So the root's children are \(R\) and the internal node \(P + Q\). A codeword of length 1 exists iff \(R\) is a leaf. (With \(n = 2\) both codewords have length 1.)
(a) Some frequency \(f_a > 2/5\) ⇒ some codeword has length 1. Suppose \(R\) is internal, built earlier by merging \(R_1, R_2\). At that time \(R_1, R_2\) were the two smallest nodes, so every other node then present was \(\ge \max(R_1, R_2)\). \(P\) and \(Q\) are sums of such nodes, so \(P, Q \ge \max(R_1, R_2)\). Where is \(a\)?
- Under \(R_1\) (or \(R_2\)): \(f_a \le R_1 \le P \le Q\), so \(P + Q + R_1 \ge 3f_a > 6/5 > 1\). Impossible.
- Under \(P\) or \(Q\): \(f_a \le Q \le R\), so \(Q + R > 4/5\) and \(P < 1/5\). Then \(R_1, R_2 \le P < 1/5\), so \(R = R_1 + R_2 < 2/5 < f_a \le R\). Contradiction.
So \(R\) is a leaf: a codeword of length 1 exists.
HW4 P5 version (the winner gets one bit). Let the winner \(w\) have the most votes and \(f_w > 0.4\). By (a), \(R\) is a leaf. If \(R \ne w\), then \(w\) lies under \(P\) or \(Q\), so \(f_w \le Q \le R\): candidate \(R\) has at least as many votes as the winner, contradicting that \(w\) is the unique top vote-getter. So \(w\)'s codeword is 1 bit. (If two candidates tie for first, Huffman's tie-break decides which one gets the 1-bit code.) Running time: tallying \(N\) ballots is \(O(N)\); Huffman on \(n\) candidates with a heap is \(O(n \log n)\).
Careful: (a) says "some codeword has length 1", not "the character above 2/5 gets it". Frequencies 0.41, 0.49, 0.10: Huffman merges 0.10 + 0.41, so the 0.41 symbol gets 2 bits and 0.49 gets the 1-bit code.
(b) All frequencies \(< 1/3\) ⇒ no codeword of length 1. Suppose \(R\) is a leaf. Then \(P + Q = 1 - R > 2/3\). But \(P \le Q \le R < 1/3\) gives \(P + Q < 2/3\). Contradiction.
DPV 5.17: longest possible codeword
Answer: \(n - 1\). A full binary tree with \(n\) leaves has \(n - 1\) internal nodes, and a root-to-leaf path passes through distinct internal nodes, so depth \(\le n - 1\). It is reached when every merge uses the node just created, i.e. each new symbol is at least the sum of all smaller ones. Example: Fibonacci frequencies 1, 1, 2, 3, 5, 8 give lengths 5, 5, 4, 3, 2, 1 (script: a = 11110, b = 11111, c = 1110, d = 110, e = 10, f = 0). Powers of two also work: \(1/2, 1/4, \dots, 1/2^{n-1}, 1/2^{n-1}\) (DPV 5.14 is the \(n = 5\) case, longest codeword 4).
6 · Horn formulas
Do: start with everything false. While some implication has all left-side variables true and its right side false, set the right side true. Then check every pure negative clause.
set all variables to false
while there is an implication that is not satisfied:
set the right-hand variable of the implication to true
if all pure negative clauses are satisfied: return the assignment
else: return "formula is not satisfiable"
Why correct: invariant "every variable set to true must be true in every satisfying assignment" (each was forced). So the result has the fewest true variables possible. If it breaks a negative clause, any satisfying assignment would have even more true variables and break it too. Can be implemented in linear time in the formula length.
Lecture example. \((w \wedge y \wedge z) \Rightarrow x\), \((x \wedge z) \Rightarrow w\), \(x \Rightarrow y\), \(\Rightarrow x\), \((x \wedge y) \Rightarrow w\), \((\bar w \vee \bar x \vee \bar z)\), \(\bar z\).
- \(\Rightarrow x\): set \(x\) true.
- \(x \Rightarrow y\): set \(y\) true.
- \((x \wedge y) \Rightarrow w\): set \(w\) true.
- No other implication fires (both remaining ones need \(z\)). \(z\) stays false.
- Negative clauses: \(\bar w \vee \bar x \vee \bar z\) holds (\(z\) false); \(\bar z\) holds. Satisfiable: \(w, x, y\) true, \(z\) false.
7 · Set cover (greedy approximation)
Do: repeatedly pick the set covering the most uncovered elements. Not optimal, but within a \(\ln n\) factor.
Not optimal: \(B = \{1..6\}\), \(S_1 = \{1,2,3\}\), \(S_2 = \{4,5,6\}\), \(S_3 = \{1,2,4,5\}\). Greedy takes \(S_3\) (4 new), then \(S_1\) and \(S_2\): 3 sets. Optimum \(S_1, S_2\): 2 sets.
Claim. If \(|B| = n\) and the optimal cover uses \(k\) sets, greedy uses at most \(k \ln n\) sets.
Proof (lecture). Let \(n_t\) = number of elements still uncovered after \(t\) greedy steps; \(n_0 = n\). The \(k\) optimal sets cover the remaining \(n_t\) elements, so one of them covers at least \(n_t / k\) of them. Greedy picks a set at least that good:
Uses \(1 - x \le e^{-x}\), with equality only at \(x = 0\). At \(t = k\ln n\): \(n_t < n e^{-\ln n} = 1\), so nothing is left uncovered. Approximation factor \(\le \ln n\).
8 · HW4 P4: the "fewest enemies" party greedy fails
Greedy: repeatedly invite the eligible person with the fewest enemies. Counterexample (7 friends, 12 enemy pairs):
- Enemy counts: X 2; A, B, C, D 3 each; E, F 5 each.
- Greedy invites X first (fewest enemies). That rules out A and B. Left: C, D, E, F, who are all enemies of each other, so greedy invites one more. Greedy total: 2.
- Better: {A, B, C} (no enemy pair among them). Optimum: 3.
A brute-force script confirmed the maximum is 3 and that greedy ends with 2 under every tie-break, whether "fewest enemies" counts all enemies or only enemies still eligible. Why it fails: X looks cheap, but its two enemies A and B are compatible with each other, so X blocks two invitations to gain one.
Self-check
1. Huffman on a 45, b 13, c 12, d 16, e 9, f 5 (thousands of chars). Codeword lengths and total bits?
Merges: f+e = 14; c+b = 25; 14+d = 30; 25+30 = 55; a+55 = 100. Lengths: a 1, b 3, c 3, d 3, e 4, f 4. Total = 14 + 25 + 30 + 55 + 100 = 224 thousand bits (vs 300 for a fixed 3-bit code).
2. True or false: if all edge weights are distinct, the MST is unique (DPV 5.6).
True. Suppose \(T_1 \ne T_2\) are both MSTs. Let \(e\) be the lightest edge in exactly one of them, say \(e \in T_1\). \(T_2 + e\) has a cycle, which contains an edge \(e' \notin T_1\) (\(T_1\) has no cycle). \(e'\) is in exactly one tree, so \(w(e') > w(e)\) (distinct, and \(e\) was the lightest such). \(T_2 + e - e'\) is lighter than \(T_2\). Contradiction.
3. Starting from {1}, {2}, {3}, {4}: union(1,2), union(3,4), union(1,3), find(1). Union by rank, path compression, ties lower → higher. Final pointers?
1→2 (rank 2 = 1); 3→4 (rank 4 = 1); union(1,3): roots 2 and 4 tie, so 2→4, rank 4 = 2. find(1): path 1→2→4, compress 1→4. Final: 1→4, 2→4, 3→4, root 4 (rank 2).
4. In DPV 5.1, is edge BC (weight 5) in some MST? Use the HW4 P2 test.
No. Path B–E (2), E–F (1), F–G (3), G–C (4) uses only edges lighter than 5, so BC is the heaviest edge on a cycle (strictly), and it is in no MST. Matches the Kruskal trace, which skips BC.
5. Horn: \(\Rightarrow a\), \(a \Rightarrow b\), \((b \wedge c) \Rightarrow d\), \((\bar a \vee \bar c)\). Satisfiable?
Start all false. \(\Rightarrow a\): a true. \(a \Rightarrow b\): b true. \((b \wedge c) \Rightarrow d\) does not fire (c false). Negative clause \(\bar a \vee \bar c\) holds since c is false. Satisfiable: a, b true; c, d false.
6. Prim vs Dijkstra: same code, one line differs. Which line, and which one breaks with negative edges?
Dijkstra: if dist(z) > dist(v) + w(v, z), set dist(z) = dist(v) + w(v, z). Prim: if cost(z) > w(v, z), set cost(z) = w(v, z). Both run in \(O((n + m)\log n)\) with a binary heap. Dijkstra can fail with negative edges; Prim cannot (DPV 5.9(i)).