ADVANCED ALGORITHMS - NP-COMPLETENESS:Polynomial-Time Reductions

Mastering polynomial-time reductions concepts and implementation.

Polynomial-Time Reductions

What is a Reduction?

A reduction from problem A to problem B shows that if we can solve B, we can solve A. This is the central tool for proving problems are "hard" in computational complexity theory.

Formally, problem A polynomial-time reduces to problem B (written A ≤ₚ B) if:

  • There exists a polynomial-time algorithm to transform any instance of A into an instance of B
  • The transformed instance has the same answer as the original
  • If we had a poly-time solver for B, we'd get a poly-time solver for A

Key insight: If A ≤ₚ B and B ∈ P, then A ∈ P. Equivalently, if A ≤ₚ B and A ∉ P, then B ∉ P.

Types of Reductions

Many-One (Karp) Reduction

Transform one instance of A to exactly one instance of B. This is the most restrictive and most commonly used type for NP-completeness proofs.

Turing (Cook) Reduction

Algorithm for A can make multiple oracle calls to B. More powerful, but harder to work with.

Parsimonious Reduction

Preserves the number of solutions — useful for counting problems (#P-completeness).

Example 1: Vertex Cover ≤ₚ Independent Set

Vertex Cover (VC): Find a set S of k vertices such that every edge has at least one endpoint in S.

Independent Set (IS): Find a set S of k vertices such that no two vertices in S share an edge.

Key observation: S is a vertex cover of G if and only if V − S is an independent set of G.

def vertex_cover_to_independent_set(graph, n_vertices, k):
    """
    Reduction: VC(G, k) → IS(G, n-k)
    
    G has a vertex cover of size k
    ⟺ G has an independent set of size (n - k)
    
    Proof:
    - Let C be a vertex cover of size k
    - Then V - C has size n - k
    - For any edge (u,v): at least one of u,v is in C
    - Therefore NEITHER u nor v are both in V-C
    - So V-C has no edges within it → V-C is an independent set
    """
    # Same graph, complementary parameter
    return independent_set(graph, n_vertices - k)

def vertex_cover_to_clique(graph, n_vertices, k):
    """
    Reduction: VC(G, k) → Clique(complement(G), n-k)
    
    G has a vertex cover of size k
    ⟺ complement(G) has a clique of size (n - k)
    """
    complement = build_complement(graph, n_vertices)
    return clique(complement, n_vertices - k)

Example 2: 3-SAT ≤ₚ Independent Set

This reduction proves Independent Set is NP-complete. Given a 3-SAT instance with clauses C₁, ..., Cₘ:

  1. For each clause Cᵢ = (l₁ ∨ l₂ ∨ l₃), create a triangle of 3 nodes
  2. Add a "conflict edge" between literal lᵢ in clause r and literal ¬lᵢ in clause s
  3. Set k = m (number of clauses)

Claim: 3-SAT is satisfiable ↔ graph has an independent set of size k.

def sat_to_independent_set(clauses):
    """
    Build graph for 3-SAT → Independent Set reduction.
    Returns: (graph, k) where k = len(clauses)
    """
    nodes = []  # (clause_idx, literal)
    edges = []
    node_idx = {}
    
    # Create triangle for each clause
    for i, clause in enumerate(clauses):
        for j, literal in enumerate(clause):
            idx = len(nodes)
            node_idx[(i, j)] = idx
            nodes.append((i, literal))
    
    # Intra-clause edges (triangle)
    for i, clause in enumerate(clauses):
        for j in range(len(clause)):
            for l in range(j + 1, len(clause)):
                edges.append((node_idx[(i, j)], node_idx[(i, l)]))
    
    # Inter-clause conflict edges
    for i, clause_i in enumerate(clauses):
        for j_i, lit_i in enumerate(clause_i):
            for i2, clause_i2 in enumerate(clauses):
                if i >= i2:
                    continue
                for j_i2, lit_i2 in enumerate(clause_i2):
                    # Conflict: lit_i and lit_i2 are negations of each other
                    if lit_i == -lit_i2:
                        edges.append((node_idx[(i, j_i)], node_idx[(i2, j_i2)]))
    
    return nodes, edges, len(clauses)

Example 3: Hamiltonian Path ≤ₚ Travelling Salesman

Given an undirected graph G = (V, E), build a TSP instance:

  • Assign weight 1 to each edge in E
  • Assign weight 2 to each edge NOT in E
  • Budget B = |V| − 1

G has a Hamiltonian path ↔ TSP has a tour of cost ≤ B + 2 (the +2 accounts for returning to start).

The Reduction Recipe

To prove problem X is NP-complete:

  1. Show X ∈ NP: Give a polynomial-time verifier (certificate check)
  2. Choose a known NP-complete problem Y
  3. Show Y ≤ₚ X: Give a poly-time transformation f such that
  • YES instance of Y → YES instance of X
  • NO instance of Y → NO instance of X

Practice Problems

  1. Prove that Clique ≤ₚ Independent Set
  2. Show that Hamiltonian Cycle ≤ₚ Hamiltonian Path
  3. Reduce Subset Sum to Partition
  4. Show that 3-Coloring ≤ₚ SAT