19.18 Primal-Dual Approximation
Primal-dual approximation algorithms use linear programming structure to design fast approximate solutions.
19.18 Primal-Dual Approximation
Primal-dual approximation algorithms use linear programming structure to design fast approximate solutions. The method starts from an optimization problem, writes a linear-programming relaxation, examines its dual, and then builds a solution while maintaining information about both sides.
The central idea is simple: the dual solution gives a bound on the optimum. If the algorithm constructs a feasible primal solution whose cost is at most some factor times the dual value, then the same factor bounds the approximation ratio.
This method is especially useful for covering and network-design problems, where greedy reasoning alone can hide the underlying structure.
Problem
You need to solve a minimization problem.
The exact integer program is hard.
You can write:
Primal problem:
minimize cost
subject to coverage constraints
and its dual:
Dual problem:
maximize lower bound
subject to packing constraints
The dual gives a certificate that the optimum cannot be smaller than a certain value.
The goal is to grow that certificate while constructing a feasible approximate solution.
Linear Programming Relaxation
Consider Vertex Cover.
Given an undirected graph:
G = (V, E)
choose a minimum-size set of vertices touching every edge.
Integer program:
minimize Σ xᵥ
subject to:
xᵤ + xᵥ ≥ 1 for every edge (u, v)
xᵥ ∈ {0, 1}
The relaxation replaces:
xᵥ ∈ {0, 1}
with:
xᵥ ≥ 0
or commonly:
0 ≤ xᵥ ≤ 1
Now fractional values are allowed.
The Dual
The dual introduces one variable per edge:
yₑ
Dual problem:
maximize Σ yₑ
subject to:
Σ yₑ ≤ 1 for every vertex v
over edges e incident to v
yₑ ≥ 0
Interpretation:
Each edge wants to raise money.
Each vertex has a budget limit of 1.
The total dual value is a lower bound on the optimal vertex cover cost.
Weak Duality
Weak duality says:
Any feasible dual value
≤
Any feasible primal value
For minimization problems, this means the dual is a lower bound.
If:
dual value = 10
then:
OPT ≥ 10
If an algorithm returns a solution of cost:
20
then it has proved a 2-approximation:
ALG = 20 ≤ 2 · 10 ≤ 2 · OPT
This is the primal-dual proof pattern.
Recipe: Primal-Dual Vertex Cover
Start with:
cover = ∅
yₑ = 0 for every edge
While there is an uncovered edge (u, v):
- Increase
y_(u,v)until one endpoint becomes tight. - Add tight endpoints to the cover.
- Remove all edges covered by those endpoints.
For unweighted Vertex Cover, this simplifies to the matching-based 2-approximation: pick an uncovered edge and add both endpoints.
from collections.abc import Iterable
Edge = tuple[str, str]
def primal_dual_vertex_cover(edges: Iterable[Edge]) -> set[str]:
uncovered = set(edges)
cover: set[str] = set()
while uncovered:
u, v = next(iter(uncovered))
cover.add(u)
cover.add(v)
uncovered = {
(a, b)
for (a, b) in uncovered
if a not in cover and b not in cover
}
return cover
This version does not explicitly store dual variables, but the selected uncovered edges behave as the positive dual variables.
Example
Graph edges:
(A, B)
(B, C)
(C, D)
(D, E)
Select uncovered edge:
(A, B)
Add:
A, B
Covered edges removed:
(A, B)
(B, C)
Remaining:
(C, D)
(D, E)
Select:
(C, D)
Add:
C, D
All edges are now covered.
Returned cover:
{A, B, C, D}
The selected dual edges are:
(A, B)
(C, D)
They form a matching.
Every vertex cover must include at least one endpoint of each selected edge, so:
OPT ≥ 2 selected edges?
Careful: since selected edges are disjoint, every cover must pick at least one endpoint per selected edge.
Thus:
OPT ≥ number of selected edges
The algorithm selects two vertices per selected edge:
ALG = 2 · number of selected edges
Therefore:
ALG ≤ 2 · OPT
Weighted Vertex Cover
Primal-dual reasoning becomes more valuable in weighted problems.
Each vertex has a cost:
cᵥ
Integer program:
minimize Σ cᵥ xᵥ
subject to:
xᵤ + xᵥ ≥ 1 for every edge (u, v)
xᵥ ∈ {0, 1}
Dual:
maximize Σ yₑ
subject to:
Σ yₑ ≤ cᵥ for every vertex v
over edges e incident to v
yₑ ≥ 0
Now each vertex has a budget equal to its cost.
Recipe: Weighted Primal-Dual Vertex Cover
Algorithm:
- Start with empty cover.
- Start all dual variables at zero.
- Pick an uncovered edge
(u, v). - Increase its dual value until either
uorvbecomes tight. - Add any tight endpoint to the cover.
- Remove edges covered by the chosen vertices.
- Repeat.
A vertex is tight when:
sum of incident selected dual values = vertex cost
This gives a 2-approximation for weighted Vertex Cover.
Implementation Sketch
from collections import defaultdict
from dataclasses import dataclass
@dataclass(frozen=True)
class WeightedEdge:
u: str
v: str
def weighted_vertex_cover(
edges: list[WeightedEdge],
costs: dict[str, float],
) -> set[str]:
uncovered = set(edges)
cover: set[str] = set()
load: dict[str, float] = defaultdict(float)
while uncovered:
edge = next(iter(uncovered))
u, v = edge.u, edge.v
slack_u = costs[u] - load[u]
slack_v = costs[v] - load[v]
delta = min(slack_u, slack_v)
load[u] += delta
load[v] += delta
if abs(load[u] - costs[u]) < 1e-12:
cover.add(u)
if abs(load[v] - costs[v]) < 1e-12:
cover.add(v)
uncovered = {
e
for e in uncovered
if e.u not in cover and e.v not in cover
}
return cover
This implementation focuses on the algorithmic idea. Production code should avoid floating-point tightness checks when costs are integral or rational.
Why the Approximation Is 2
Let:
C
be the returned cover.
Each chosen vertex is tight:
cᵥ = Σ yₑ
over incident edges.
So:
cost(C)
=
Σ cᵥ over v in C
becomes:
Σ over v in C
Σ yₑ incident to v
Each edge has at most two endpoints.
Therefore every dual variable is counted at most twice:
cost(C) ≤ 2Σ yₑ
By weak duality:
Σ yₑ ≤ OPT
So:
cost(C) ≤ 2OPT
That proves the 2-approximation.
Set Cover Connection
Weighted Set Cover also has a primal-dual view.
Primal:
Choose sets
so every element is covered
at minimum total cost.
Dual:
Assign prices to elements
so no set is overcharged.
Greedy Set Cover can be interpreted as repeatedly pricing uncovered elements and choosing sets when they become cost-effective.
This connection explains why many covering problems have logarithmic or constant-factor approximation algorithms.
Facility Location Connection
Facility location problems also fit naturally.
Primal decisions:
Open facilities.
Assign clients.
Dual variables:
Client contributions.
A facility opens when enough client contributions pay for it.
This “raise dual variables until a constraint becomes tight” pattern is common in network design and clustering.
General Primal-Dual Template
For a minimization problem:
- Write an LP relaxation.
- Write its dual.
- Start with an infeasible or partial primal solution.
- Maintain a feasible dual solution.
- Raise dual variables until a primal action becomes justified.
- Add primal objects.
- Prove the final primal cost is bounded by a factor times the dual value.
The proof usually follows directly from weak duality and bounded overcounting.
Why This Method Works
The dual tells the algorithm what the instance can “afford.”
Instead of choosing objects only by local benefit, the algorithm grows a lower bound on the optimum. When a primal object becomes tight, the algorithm has evidence that selecting it is justified.
This produces algorithms that are greedy in behavior but LP-guided in analysis.
Complexity
The running time depends on how constraints are represented.
For the simple vertex cover implementation:
| Operation | Cost |
|---|---|
| Pick uncovered edge | O(1) average |
| Remove covered edges | O(m) naive |
| Total naive cost | O(m²) worst case |
With better adjacency structures, the implementation can be improved.
In many primal-dual algorithms, the mathematical design is clean, while the engineering challenge lies in maintaining tight constraints efficiently.
Common Mistakes
Do not use a primal-dual proof without verifying dual feasibility.
Do not assume every greedy algorithm has a primal-dual interpretation.
Do not forget the overcounting factor. It usually determines the approximation ratio.
Do not rely on floating-point equality for tightness in serious implementations.
Do not confuse LP relaxation optimality with integer optimality. The gap between them is exactly what approximation analysis must control.
Takeaway
Primal-dual approximation algorithms build a solution and a proof at the same time. The dual variables provide a lower bound on the optimum. The primal solution provides a feasible answer. If the algorithm can show that its primal cost is at most α times the dual value, weak duality immediately gives an α-approximation. This method is especially effective for covering, network design, facility location, and scheduling problems.