08
Bron–Kerbosch in C++
The classical algorithm for enumerating maximal cliques. With pivoting, it is roughly the best you can do.
- Represent the graph as adjacency bitsets — for n ≤ 64 vertices, a single uint64_t per vertex. Set intersection becomes a bitwise AND, which is the inner loop of clique enumeration.
#include <cstdint> #include <vector> using Bits = uint64_t; std::vector<Bits> adj; // adj[v] = neighbors of v as bitmask - Bron–Kerbosch with Tomita's pivot. R is the clique being built, P the candidates, X the already-explored. The pivot u minimizes branching by skipping anyone adjacent to it.
void bk(Bits R, Bits P, Bits X, std::vector<Bits>& out) { if (P == 0 && X == 0) { out.push_back(R); return; } Bits PX = P | X; int pivot = __builtin_ctzll(PX); Bits cand = P & ~adj[pivot]; while (cand) { int v = __builtin_ctzll(cand); Bits vbit = 1ULL << v; bk(R | vbit, P & adj[v], X & adj[v], out); P &= ~vbit; X |= vbit; cand &= ~vbit; } } - Drive it from main, then keep the largest result. The bitset trick (__builtin_ctzll picks the lowest set bit; popcount counts set bits) is the reason this kernel screams on modern CPUs.
Bits all = (1ULL << n) - 1; std::vector<Bits> cliques; bk(0, all, 0, cliques); auto best = *std::max_element(cliques.begin(), cliques.end(), [](Bits a, Bits b){ return __builtin_popcountll(a) < __builtin_popcountll(b); }); - For n > 64, swap uint64_t for std::vector<uint64_t> and the same idea generalizes. Industrial implementations (like cliquer) add degeneracy ordering on top — a 2× to 10× speedup on real-world graphs.
// std::bitset<N> works too if N is known at compile time. // For variable n, std::vector<uint64_t> with manual word ops // stays cache-friendly enough.