Skip to content

perf(cliques): pivot in Bron-Kerbosch instead of branching on every candidate - #803

Merged
samueltardieu merged 1 commit into
evenfurther:mainfrom
tachsin:perf/cliques-pivot
Sep 9, 2026
Merged

perf(cliques): pivot in Bron-Kerbosch instead of branching on every candidate#803
samueltardieu merged 1 commit into
evenfurther:mainfrom
tachsin:perf/cliques-pivot

Conversation

@tachsin

@tachsin tachsin commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Closes #801.

Adds the pivot to Bron-Kerbosch. A maximal clique either leaves the pivot out or contains one of its neighbours, so the candidates adjacent to the pivot are not branched on at this level and are reached through a deeper call instead. The same cliques come out, in a different order.

The three working sets also become vectors. They are only ever filtered and scanned, never looked up by key, and they shrink quickly with depth, so hashing every vertex at every level costs more than the linear scans it saves. That removes the per-branch clone of the candidate set and two hash set allocations per candidate.

Worth reviewing

connected is caller-supplied and may report a vertex as connected to itself. The previous code was indifferent to that; pivoting is not, because a pivot excluding itself from its own branch set could drop a candidate that is then never branched on, ending the enumeration early. A vertex is therefore never treated as its own neighbour, which keeps both conventions working. This is the behavioural assumption raised in #801.

The order in which cliques are emitted changes. It was not specified before either — it followed hash iteration order — and the vertices are now deduplicated in the order given, so it is at least deterministic.

Checking

Verified against a brute-force enumeration of all 2^n subsets over 60 random graphs, with connected reflexive on half the rounds and irreflexive on the other half. The two existing tests are unchanged.

About 88% off a 130 vertex graph with edge probability 0.5, measured against this branch's parent.

All timings measured on Windows 11, Intel Core Ultra 7 265K, 64 GB RAM.

…andidate

The implementation branches on every remaining candidate in turn, so a clique
of k vertices is rediscovered through many of the orders its vertices can be
reached in, and each of those branches allocates three hash sets on the way
down.

Choose a pivot from the candidates and the excluded set, as Bron and Kerbosch
describe: a maximal clique either leaves the pivot out or contains one of its
neighbours, so the candidates adjacent to the pivot need not be branched on
here and are reached through a deeper call instead. The same cliques come out,
in a different order.

The three working sets become vectors. They are only ever filtered and scanned,
never looked up by key, and they shrink quickly with depth, so hashing every
vertex at every level costs more than the linear scans it saves.

One subtlety: `connected` is a caller-supplied predicate and is free to report
a vertex as connected to itself, which the previous code was indifferent to.
Pivoting is not: a pivot excluding itself from its own branch set could drop a
candidate that is never branched on, and the enumeration would stop early. A
vertex is therefore never treated as its own neighbour. Both conventions are
covered by the tests.

Checked exhaustively against a brute-force enumeration of every subset over 60
random graphs.

About 88% off a 130 vertex graph with edge probability 0.5.
@samueltardieu
samueltardieu added this pull request to the merge queue Sep 9, 2026
Merged via the queue into evenfurther:main with commit 1ba5924 Sep 9, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

maximal_cliques rediscovers each clique through many orderings for want of a pivot

2 participants