Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Graphs / Matching and Assignment

4.6.3 Minimum Vertex Cover (Konig)

4-Graphs/4.6.3_Minimum_Vertex_Cover_(Konig).cpp

Given a bipartite graph with left nodes $A = \{0, \ldots, n_1 - 1\}$ and right nodes $B = \{0, \ldots, n_2 - 1\}$, find a minimum vertex cover: a smallest set of nodes such that every edge has at least one endpoint in the set. By Konig's theorem, in a bipartite graph the size of a minimum vertex cover equals the size of a maximum matching, and the nodes left uncovered form a maximum independent set.

The construction first computes a maximum matching with Kuhn's algorithm. It then marks every left node reachable from an unmatched left node by an alternating path (following unmatched edges from left to right and matched edges from right to left). Konig's theorem says the minimum vertex cover is exactly the left nodes that are not reachable together with the right nodes that are reachable; each matched edge contributes exactly one endpoint, so the cover size equals the matching size.

  • min_vertex_cover(n2) returns the nodes of a minimum vertex cover for the global adjacency list adj (left nodes $[0, {\htmlClass{math-inline-code}{\texttt{n1}}})$, right neighbors $[0, {\htmlClass{math-inline-code}{\texttt{n2}}})$), where n1 is adj.size(). Each returned value is a left node i in $[0, {\htmlClass{math-inline-code}{\texttt{n1}}})$, or a right node j encoded as n1 + j.

The adjacency list adj is shared with the matching routine: adj[u] lists the right-side neighbors of left node u. Cover nodes are reported with right node j encoded as n1 + j so the two sides share one index space.

Implementation

#include <vector>

std::vector<std::vector<int>> adj;
std::vector<int> match_left, match_right, visit;
int timer;

bool dfs(int u) {
  visit[u] = timer;
  for (int nb : adj[u]) {
    if (match_right[nb] == -1) {
      match_left[u] = nb;
      match_right[nb] = u;
      return true;
    }
  }
  for (int nb : adj[u]) {
    int v = match_right[nb];
    if (visit[v] != timer && dfs(v)) {
      match_left[u] = nb;
      match_right[nb] = u;
      return true;
    }
  }
  return false;
}

int bipartite_matching(int n2) {
  int n1 = static_cast<int>(adj.size());
  match_left.assign(n1, -1);
  match_right.assign(n2, -1);
  visit.assign(n1, 0);
  timer = 0;
  int matches = 0;
  for (int i = 0; i < n1; i++) {
    timer++;
    if (dfs(i)) {
      matches++;
    }
  }
  return matches;
}

std::vector<int> min_vertex_cover(int n2) {
  int n1 = static_cast<int>(adj.size());
  bipartite_matching(n2);
  std::vector<char> reachable_left(n1), reachable_right(n2);
  std::vector<int> st;
  for (int u = 0; u < n1; u++) {
    if (match_left[u] == -1) {
      reachable_left[u] = true;
      st.push_back(u);
    }
  }
  while (!st.empty()) {
    int u = st.back();
    st.pop_back();
    for (int v : adj[u]) {
      if (v != match_left[u] && !reachable_right[v]) {
        reachable_right[v] = true;
        int next = match_right[v];
        if (next != -1 && !reachable_left[next]) {
          reachable_left[next] = true;
          st.push_back(next);
        }
      }
    }
  }
  std::vector<int> cover;
  for (int u = 0; u < n1; u++) {
    if (!reachable_left[u]) {
      cover.push_back(u);
    }
  }
  for (int v = 0; v < n2; v++) {
    if (reachable_right[v]) {
      cover.push_back(n1 + v);
    }
  }
  return cover;
}

Example Usage

#include <algorithm>
#include <cassert>
using namespace std;

int main() {
  // Left nodes a0..a2, right nodes b0..b3:
  //   a0 -- b1
  //   a1 -- b0, b1, b2
  //   a2 -- b2, b3
  int n1 = 3, n2 = 4;
  adj.assign(n1, {});
  adj[0].push_back(1);
  adj[1].push_back(0);
  adj[1].push_back(1);
  adj[1].push_back(2);
  adj[2].push_back(2);
  adj[2].push_back(3);
  vector<int> cover = min_vertex_cover(n2);
  assert(cover.size() == 3);  // Equals the maximum matching size, by Konig's theorem.
  // The cover is valid: every edge has at least one endpoint in it.
  for (int u = 0; u < n1; u++) {
    for (int v : adj[u]) {
      bool left_in = find(cover.begin(), cover.end(), u) != cover.end();
      bool right_in = find(cover.begin(), cover.end(), n1 + v) != cover.end();
      assert(left_in || right_in);
    }
  }
  return 0;
}