Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Approximation Algorithms

Throughout this book we have designed algorithms that solve problems exactly and efficiently. But in the previous chapter we saw that many important optimization problems — minimum vertex cover, set cover, traveling salesman — are NP-hard: no polynomial-time algorithm is known, and most researchers believe none exists. Approximation algorithms offer a powerful middle ground: polynomial-time algorithms that produce solutions provably close to optimal. Instead of finding the best solution, we settle for one that is guaranteed to be within a known factor of the best. In this chapter we formalize approximation ratios, then study three classical algorithms: a 2-approximation for vertex cover, a greedy -approximation for set cover, and a 2-approximation for metric TSP via minimum spanning trees.

When exact solutions are infeasible

Chapter 21 demonstrated that brute-force approaches to NP-hard problems are impractical for all but the smallest inputs. A brute-force TSP solver exhausts permutations, which is infeasible beyond about 12–15 cities. A brute-force subset sum examines subsets, limiting us to roughly 30 elements.

For real-world instances — routing delivery trucks through hundreds of stops, selecting facilities to cover a service region, or allocating resources across a network — we need algorithms that:

  1. Run in polynomial time (ideally or , not ).
  2. Provide a quality guarantee — we can bound how far the solution is from optimal.

Approximation algorithms deliver both.

Approximation ratios

Let be a polynomial-time algorithm for an optimization problem, and let denote the cost of an optimal solution for instance .

Definition. Algorithm has approximation ratio if, for every instance of size :

The ratio is always . For minimization problems, . For maximization problems, .

An algorithm with approximation ratio is called a -approximation algorithm.

Some important distinctions:

  • A constant-factor approximation has for some constant (e.g., the 2-approximation for vertex cover).
  • A logarithmic approximation has (e.g., greedy set cover).
  • A polynomial-time approximation scheme (PTAS) achieves ratio for any constant , though the running time may depend on .
  • A fully polynomial-time approximation scheme (FPTAS) is a PTAS whose running time is polynomial in both and .

Not all NP-hard problems can be approximated equally well. Under standard complexity assumptions:

ProblemBest known ratioHardness of approximation
Vertex cover2Cannot do better than unless P = NP
Set coverCannot do better than unless P = NP
Metric TSP1.5 (Christofides)Cannot do better than unless P = NP
General TSPNo constant-factor approximation unless P = NP
MAX-3SAT7/8Cannot do better than unless P = NP
KnapsackFPTASHas a -approximation for any

Vertex cover: 2-approximation

Problem definition

Given an undirected graph , a vertex cover is a subset such that every edge in has at least one endpoint in . The minimum vertex cover problem asks for a cover of smallest size.

Vertex cover is one of Karp's 21 NP-complete problems (1972) and has a natural relationship to the independent set problem: is an independent set if and only if is a vertex cover.

The algorithm

The 2-approximation is elegantly simple:

  1. Start with an empty cover and the full edge set .
  2. Pick an arbitrary uncovered edge from .
  3. Add both endpoints and to .
  4. Remove all edges incident to or from .
  5. Repeat until is empty.

The key insight is that the edges we pick in step 2 form a matching — a set of edges that share no endpoints. Every vertex cover must include at least one endpoint of each matching edge, so . Our algorithm adds exactly 2 vertices per matching edge, giving .

Pseudocode

APPROX-VERTEX-COVER(G):
    C ← ∅
    E' ← E
    while E' ≠ ∅:
        pick any edge (u, v) ∈ E'
        C ← C ∪ {u, v}
        remove all edges incident to u or v from E'
    return C

Proof of the 2-approximation

Claim: .

Proof. Let be the set of edges selected by the algorithm. By construction:

  1. No two edges in share an endpoint (each time we select an edge, we remove all incident edges). So is a matching.
  2. The algorithm adds both endpoints of each matching edge: .
  3. Any vertex cover must include at least one endpoint of every edge, including every edge in . Since matching edges are disjoint, the optimal cover needs at least vertices: .
  4. Therefore .

TypeScript implementation

import { Graph } from '../12-graphs-and-traversal/graph.js';

export interface VertexCoverResult<T> {
  cover: Set<T>;
  size: number;
}

export function vertexCover<T>(graph: Graph<T>): VertexCoverResult<T> {
  if (graph.directed) {
    throw new Error('Vertex cover requires an undirected graph');
  }

  const cover = new Set<T>();
  const edges = graph.getEdges();

  for (const edge of edges) {
    // If neither endpoint is already covered, add both.
    if (!cover.has(edge.from) && !cover.has(edge.to)) {
      cover.add(edge.from);
      cover.add(edge.to);
    }
  }

  return { cover, size: cover.size };
}

Note that the implementation iterates over edges and skips any edge that already has a covered endpoint — this is equivalent to "removing incident edges" in the pseudocode, since we only select an edge when both endpoints are uncovered.

Complexity:

  • Time: — we iterate over all edges once.
  • Space: — for the edge list and the cover set.

Worked example

Consider this graph:

    1 --- 2
    |     |
    3 --- 4 --- 5

Edges: (1,2), (1,3), (2,4), (3,4), (4,5).

Suppose the algorithm processes edges in order:

  1. Pick (1,2): add 1 and 2 to . Remove (1,2), (1,3), (2,4).
  2. Remaining edges: (3,4), (4,5). Pick (3,4): add 3 and 4 to . Remove (3,4), (4,5).
  3. No edges remain. , .

The matching was , so .

The optimal cover is or with . Our algorithm returned , which is exactly the worst case of the 2-approximation guarantee.

Tightness of the bound

The factor of 2 is tight for this algorithm. Consider the complete bipartite graph with vertices on each side. The optimal vertex cover selects one side: . A maximal matching has edges (one from each left vertex to a right vertex), and the algorithm adds both endpoints: .

Whether vertex cover can be approximated with a ratio better than 2 in polynomial time is a major open problem. The best known lower bound (assuming the Unique Games Conjecture) is for any .

Greedy set cover: -approximation

Problem definition

Given a universe and a collection of subsets of whose union is , the set cover problem asks for the smallest sub-collection of that covers every element of .

Set cover is a fundamental NP-hard problem that generalizes vertex cover (each vertex corresponds to a "set" of its incident edges, and the universe is the edge set).

The greedy algorithm

The greedy strategy is intuitive: at each step, select the subset that covers the most currently-uncovered elements.

GREEDY-SET-COVER(U, S):
    C ← ∅                  // selected subsets
    uncovered ← U
    while uncovered ≠ ∅:
        select S_i ∈ S maximizing |S_i ∩ uncovered|
        C ← C ∪ {S_i}
        uncovered ← uncovered \ S_i
    return C

Proof of the -approximation

Theorem. The greedy algorithm produces a cover of size at most , where is the -th harmonic number.

Proof sketch. We use a charging argument. When the greedy algorithm selects a set that covers new elements, we "charge" each newly covered element a cost of .

Consider any element that was covered when elements remained uncovered. The greedy choice covers at least elements (because the optimal solution uses sets to cover everything, so by pigeonhole, some set covers at least of the remaining elements). So element 's charge is at most .

Summing over all elements in the order they were covered:

TypeScript implementation

export interface SetCoverResult<T> {
  selectedIndices: number[];
  selectedSets: ReadonlySet<T>[];
  count: number;
}

export function setCover<T>(
  universe: ReadonlySet<T>,
  subsets: readonly ReadonlySet<T>[],
): SetCoverResult<T> {
  if (universe.size === 0) {
    return { selectedIndices: [], selectedSets: [], count: 0 };
  }

  const uncovered = new Set<T>(universe);
  const selectedIndices: number[] = [];
  const selectedSets: ReadonlySet<T>[] = [];
  const used = new Set<number>();

  while (uncovered.size > 0) {
    let bestIndex = -1;
    let bestCount = 0;

    for (let i = 0; i < subsets.length; i++) {
      if (used.has(i)) continue;
      let count = 0;
      for (const elem of subsets[i]!) {
        if (uncovered.has(elem)) count++;
      }
      if (count > bestCount) {
        bestCount = count;
        bestIndex = i;
      }
    }

    if (bestIndex === -1 || bestCount === 0) {
      throw new Error(
        'Subsets do not cover the entire universe; ' +
          `${uncovered.size} element(s) remain uncovered`,
      );
    }

    used.add(bestIndex);
    selectedIndices.push(bestIndex);
    selectedSets.push(subsets[bestIndex]!);

    for (const elem of subsets[bestIndex]!) {
      uncovered.delete(elem);
    }
  }

  return { selectedIndices, selectedSets, count: selectedIndices.length };
}

Complexity:

  • Time: in the worst case. Each of the at most iterations scans all subsets, and each scan examines up to elements.
  • Space: .

Worked example

Universe:

Subsets:

SetElements

Iteration 1: Uncovered = .

  • covers 3 elements, covers 2, covers 3, covers 2.
  • Tie between and ; pick .
  • Uncovered = .

Iteration 2: covers 1 (), covers 2 (), covers 2 (). Pick .

  • Uncovered = .

Iteration 3: covers 0, covers 1 (). Pick .

  • Uncovered = .

Result: , 3 subsets. The optimal solution is also 3 (e.g., ), so the greedy algorithm found an optimal solution in this case.

Optimality of the greedy bound

The approximation ratio is essentially the best possible for set cover. Under standard complexity assumptions, no polynomial-time algorithm can achieve a ratio better than for any .

Metric TSP: 2-approximation via MST

Problem definition

The Traveling Salesman Problem (TSP) asks for the shortest Hamiltonian cycle (a tour visiting every vertex exactly once and returning to the start) in a complete weighted graph.

General TSP is not only NP-hard but also inapproximable: no polynomial-time algorithm can achieve any constant approximation ratio unless P = NP. (The proof: if we could approximate within any factor , we could solve the NP-complete Hamiltonian cycle problem by assigning weight 1 to existing edges and weight to missing edges.)

However, many practical TSP instances satisfy the triangle inequality: for all vertices :

This holds for Euclidean distances, shortest-path distances in networks, and most other natural distance metrics. The resulting metric TSP admits constant-factor approximations.

The MST-based algorithm

The algorithm exploits a fundamental relationship between MSTs and optimal tours:

  1. Compute an MST of the complete graph.
  2. Perform a DFS preorder traversal of the MST.
  3. The preorder sequence, with a return edge to the start, forms the tour.
APPROX-METRIC-TSP(G, d):
    T ← MST(G)                           // Prim's or Kruskal's
    tour ← DFS-PREORDER(T, starting from vertex 0)
    return tour

Why this works: the shortcutting argument

Consider the full walk of the MST: start at the root, and traverse every edge twice (once going down, once returning). This walk visits every vertex but may visit some vertices multiple times. Its total cost is exactly , where is the MST weight.

The preorder traversal is a shortcut of this full walk: whenever the walk would revisit an already-visited vertex, we skip directly to the next unvisited vertex. By the triangle inequality, skipping vertices can only decrease the total distance:

So the shortcutted tour costs at most .

Proof of the 2-approximation

Claim: The MST-based tour has cost at most .

Proof.

  1. MST OPT: Removing any edge from the optimal tour yields a spanning tree. Since the MST is the minimum-weight spanning tree: .
  2. Tour 2 MST: The full walk costs , and the shortcutted preorder tour costs at most this (by the triangle inequality).
  3. Combining: .

TypeScript implementation

import type { DistanceMatrix } from '../21-complexity/tsp-brute-force.js';
import { Graph } from '../12-graphs-and-traversal/graph.js';
import { prim } from '../14-minimum-spanning-trees/prim.js';

export interface MetricTSPResult {
  tour: number[];
  distance: number;
}

export function metricTSP(dist: DistanceMatrix): MetricTSPResult {
  const n = dist.length;

  if (n === 0) throw new RangeError('distance matrix must not be empty');
  for (let i = 0; i < n; i++) {
    if (dist[i]!.length !== n) {
      throw new Error(
        `distance matrix must be square (row ${i} has ` +
          `${dist[i]!.length} columns, expected ${n})`,
      );
    }
  }
  if (n === 1) return { tour: [0], distance: 0 };
  if (n === 2) {
    return { tour: [0, 1], distance: dist[0]![1]! + dist[1]![0]! };
  }

  // Build a complete undirected graph.
  const graph = new Graph<number>(false);
  for (let i = 0; i < n; i++) graph.addVertex(i);
  for (let i = 0; i < n; i++) {
    for (let j = i + 1; j < n; j++) {
      graph.addEdge(i, j, dist[i]![j]!);
    }
  }

  // Step 1: Compute MST.
  const mst = prim(graph, 0);

  // Build MST adjacency list.
  const mstAdj = new Map<number, number[]>();
  for (let i = 0; i < n; i++) mstAdj.set(i, []);
  for (const edge of mst.edges) {
    mstAdj.get(edge.from)!.push(edge.to);
    mstAdj.get(edge.to)!.push(edge.from);
  }

  // Step 2: DFS preorder traversal.
  const tour: number[] = [];
  const visited = new Set<number>();

  function dfsPreorder(v: number): void {
    visited.add(v);
    tour.push(v);
    for (const neighbor of mstAdj.get(v)!) {
      if (!visited.has(neighbor)) dfsPreorder(neighbor);
    }
  }

  dfsPreorder(0);

  // Step 3: Compute tour distance.
  let distance = 0;
  for (let i = 0; i < tour.length - 1; i++) {
    distance += dist[tour[i]!]![tour[i + 1]!]!;
  }
  distance += dist[tour[tour.length - 1]!]![tour[0]!]!;

  return { tour, distance };
}

Complexity:

  • Time: — constructing the complete graph is , and Prim's algorithm on a complete graph with a binary heap is .
  • Space: for the adjacency list of the complete graph.

Worked example

Consider 4 cities at the corners of a unit square:

  1 -------- 2
  |          |
  |          |
  0 -------- 3

Distance matrix (Euclidean):

0123
0011
1101
2101
3110

Step 1: MST (using Prim's from vertex 0):

  • Add edge 0–1 (weight 1)
  • Add edge 1–2 (weight 1)
  • Add edge 0–3 (weight 1)

MST weight = 3. MST edges: 0–1, 1–2, 0–3.

Step 2: DFS preorder from 0:

Visit 0 → visit 1 → visit 2 → backtrack to 1 → backtrack to 0 → visit 3 → backtrack to 0.

Preorder: [0, 1, 2, 3].

Step 3: Tour cost:

.

The optimal tour is also 4 (the perimeter of the square), so the approximation is exact in this case.

, MST weight = 3, and — the guarantee holds.

Christofides' algorithm: a better bound

While we implemented the 2-approximation for its simplicity, a better algorithm exists. Christofides' algorithm (1976) achieves a -approximation:

  1. Compute an MST .
  2. Find the set of vertices with odd degree in .
  3. Compute a minimum-weight perfect matching on the vertices in .
  4. Combine and to get an Eulerian multigraph.
  5. Find an Eulerian circuit.
  6. Shortcut to a Hamiltonian cycle.

The key insight is that combining the MST with a minimum perfect matching on odd-degree vertices produces an Eulerian graph (all degrees even), whose Euler tour can be shortcutted. Since the minimum matching costs at most (by a pairing argument on the optimal tour), the total cost is at most .

Christofides' algorithm remained the best known approximation for metric TSP for nearly 50 years, until a very slight improvement was achieved by Karlin, Klein, and Oveis Gharan in 2021.

Comparison of approximation algorithms

ProblemAlgorithmRatioTimeApproach
Vertex coverMatching-based2Pick both endpoints of a maximal matching
Set coverGreedyPick set covering most uncovered elements
Metric TSPMST-based2MST + DFS preorder + shortcutting
Metric TSPChristofides1.5MST + minimum matching + Euler tour

Beyond the algorithms in this chapter

Approximation algorithms form a rich and active area of research. Some important topics we have not covered include:

  • LP relaxation and rounding: Many approximation algorithms work by solving a linear programming relaxation of an integer program and then rounding the fractional solution to an integer one. This technique yields tight results for problems like weighted vertex cover and MAX-SAT.

  • Semidefinite programming: For problems like MAX-CUT, the Goemans-Williamson algorithm uses semidefinite programming to achieve an approximation ratio of approximately 0.878, which is optimal assuming the Unique Games Conjecture.

  • Primal-dual methods: These construct both a feasible solution and a lower bound simultaneously, useful for network design problems.

  • The PCP theorem: The celebrated PCP (Probabilistically Checkable Proofs) theorem provides the theoretical foundation for hardness of approximation results, showing that for many problems, achieving certain approximation ratios is as hard as solving the problem exactly.

Exercises

  1. Vertex cover on trees. Show that the minimum vertex cover of a tree can be computed exactly in polynomial time using dynamic programming. (Hint: root the tree and compute, for each vertex, the minimum cover of its subtree with and without including that vertex.) Does this contradict the NP-hardness of vertex cover?

  2. Weighted set cover. Generalize the greedy set cover algorithm to the weighted case, where each subset has a cost and we want to minimize the total cost of selected subsets. Show that the greedy algorithm (pick the set with the smallest cost per newly covered element) achieves the same approximation ratio.

  3. TSP triangle inequality failure. Construct a graph with 4 vertices where the triangle inequality is violated, and show that the MST-based algorithm produces a tour whose cost exceeds . Explain why the shortcutting argument fails.

  4. MAX-SAT approximation. Consider the following simple algorithm for MAX-SAT: independently set each variable to true with probability . Show that this randomized algorithm satisfies at least clauses in expectation when each clause has at least one literal, and at least clauses when each clause has exactly 3 literals. (Here is the number of clauses.) Can you derandomize this algorithm?

  5. Tight examples. For each of the three algorithms in this chapter, describe a family of instances where the approximation ratio approaches the proven bound. That is: find graphs where the vertex cover algorithm returns a cover of size approaching , set cover instances where the greedy algorithm uses sets, and metric TSP instances where the MST tour approaches .

Chapter summary

Approximation algorithms provide a principled approach to NP-hard optimization problems: polynomial-time algorithms with provable guarantees on solution quality.

We studied three classical examples:

  • Vertex cover 2-approximation: Pick an arbitrary uncovered edge, add both endpoints. The selected edges form a matching, and any cover needs at least one vertex per matching edge, giving a factor-2 guarantee. Runs in time.

  • Greedy set cover -approximation: Repeatedly select the subset covering the most uncovered elements. A charging argument shows the greedy cost is at most times optimal, where is the harmonic number. This ratio is essentially tight: no polynomial-time algorithm can do significantly better unless P = NP.

  • Metric TSP 2-approximation via MST: Compute a minimum spanning tree, perform a DFS preorder traversal, and return the resulting tour. The MST provides a lower bound on OPT, and the triangle inequality ensures the shortcutted tour costs at most twice the MST weight. Christofides' algorithm improves this to a -approximation.

The study of approximation algorithms reveals a rich structure within NP-hard problems. Some problems (like knapsack) admit -approximations for any . Others (like vertex cover) admit constant-factor approximations but resist improvements below specific thresholds. Still others (like general TSP) cannot be approximated at all. Understanding where a problem falls in this landscape guides us toward the most effective algorithmic approach.