目录
[Bron--Kerbosch (R, P, X) 三集合定义](#Bron–Kerbosch (R, P, X) 三集合定义)
[二、Java 完整实现](#二、Java 完整实现)
[1. 存储结构:邻接矩阵boolean\[\]\[\] adj](#1. 存储结构:邻接矩阵boolean[][] adj)
[2. bronKerbosch(R,P,X)递归核心](#2. bronKerbosch(R,P,X)递归核心)
[3. 辅助工具方法](#3. 辅助工具方法)
[4. 入口calc()初始化](#4. 入口calc()初始化)
[1. 原生 BK 缺点](#1. 原生 BK 缺点)
[2. 经典优化:Pivot 支点优化(BK-P)](#2. 经典优化:Pivot 支点优化(BK-P))
[3. 应用场景](#3. 应用场景)
[六、极大团 vs 最大团总结](#六、极大团 vs 最大团总结)
一、算法原理
Bron--Kerbosch (R, P, X) 三集合定义
- R:当前已选中构成团的顶点集合(结果集,正在构造的团)
- P:候选顶点集合:所有可以加入 R、且和 R 中所有点相邻的顶点
- X:排除顶点集合 :已经枚举过、不能再组合出新极大团的顶点(去重,避免重复生成同一个团) 递归逻辑:
- 若 P=∅∧X=∅:R 是一个极大团,保存结果;
- 依次从 P 取出顶点 v:
- 递归:BK(R∪{v},P∩N(v),X∩N(v)),N(v)是v的邻接点集合
- P=P−{v},X=X∪{v}(回溯,不再用 v 组合)
二、Java 完整实现
import java.util.*;
/**
* 极大团&最大团:Bron--Kerbosch基础回溯算法
*/
public class MaximalClique {
// 邻接矩阵存图: adj[u][v]=true 代表u、v有边相连
private final boolean[][] adj;
private final int vertexNum;
// 存储所有找到的极大团
private final List<List<Integer>> allMaximalCliques = new ArrayList<>();
// 全局保存最大团
private List<Integer> maximumClique = new ArrayList<>();
public MaximalClique(boolean[][] graph) {
this.adj = graph;
this.vertexNum = graph.length;
}
/**
* 递归核心: Bron--Kerbosch(R,P,X)
* @param R 当前团顶点
* @param P 候选集合
* @param X 排除集合
*/
private void bronKerbosch(List<Integer> R, Set<Integer> P, Set<Integer> X) {
// 终止条件:P、X都空,R是极大团
if (P.isEmpty() && X.isEmpty()) {
List<Integer> clique = new ArrayList<>(R);
allMaximalCliques.add(clique);
// 更新全局最大团
if (clique.size() > maximumClique.size()) {
maximumClique = clique;
}
return;
}
// 遍历候选集合中每个顶点v
Set<Integer> pCopy = new HashSet<>(P);
for (int v : pCopy) {
// 新候选:P ∩ N(v) 只保留v的邻居
Set<Integer> newP = getIntersection(P, getNeighbors(v));
// 新排除:X ∩ N(v)
Set<Integer> newX = getIntersection(X, getNeighbors(v));
// 选中v加入R,递归
R.add(v);
bronKerbosch(R, newP, newX);
// 回溯撤销
R.remove(R.size() - 1);
// v从候选移除、加入排除集
P.remove(v);
X.add(v);
}
}
// 获取顶点v所有邻接点
private Set<Integer> getNeighbors(int v) {
Set<Integer> neighbors = new HashSet<>();
for (int i = 0; i < vertexNum; i++) {
if (adj[v][i] && v != i) {
neighbors.add(i);
}
}
return neighbors;
}
// 求两个集合交集 A ∩ B
private Set<Integer> getIntersection(Set<Integer> a, Set<Integer> b) {
Set<Integer> res = new HashSet<>();
for (int num : a) {
if (b.contains(num)) res.add(num);
}
return res;
}
// 入口方法,启动算法
public void calc() {
List<Integer> R = new ArrayList<>();
Set<Integer> P = new HashSet<>();
Set<Integer> X = new HashSet<>();
// 初始化候选P:所有顶点 0~n-1
for (int i = 0; i < vertexNum; i++) {
P.add(i);
}
bronKerbosch(R, P, X);
}
// 获取全部极大团
public List<List<Integer>> getAllMaximalCliques() {
return allMaximalCliques;
}
// 获取最大团
public List<Integer> getMaximumClique() {
return maximumClique;
}
// 测试用例
public static void main(String[] args) {
/*
测试图:5个顶点 0,1,2,3,4
边: 0-1,0-2,1-2,2-3,3-4
极大团:[0,1,2],[2,3],[3,4]
最大团:[0,1,2] size=3
*/
int n = 5;
boolean[][] graph = new boolean[n][n];
// 添加边
graph[0][1] = graph[1][0] = true;
graph[0][2] = graph[2][0] = true;
graph[1][2] = graph[2][1] = true;
graph[2][3] = graph[3][2] = true;
graph[3][4] = graph[4][3] = true;
MaximalClique solver = new MaximalClique(graph);
solver.calc();
System.out.println("===所有极大MaximalClique===");
for (List<Integer> clique : solver.getAllMaximalCliques()) {
System.out.println(clique);
}
System.out.println("\n===最大MaximumClique===");
System.out.println(solver.getMaximumClique());
}
}
三、运行输出
===所有极大MaximalClique===
[0, 1, 2]
[2, 3]
[3, 4]
===最大MaximumClique===
[0, 1, 2]
四、逐段代码解析
1. 存储结构:邻接矩阵boolean[][] adj
无向图:adj[u][v]=adj[v][u]=true 代表两点连通,适合小规模图;百万级顶点改用 ** 邻接表List<Set<Integer>> adj** 优化。
2. bronKerbosch(R,P,X)递归核心
- 终止分支
P.isEmpty() && X.isEmpty()P空:没有任何顶点可以继续扩充当前团 R;X空:没有遗漏的回溯分支,因此 R 是极大团,存入全局集合。 - 遍历每个候选 v ∈ P
newP = P ∩ N(v):新候选只能是 v 的邻居,保证加入后全互连(团定义:任意两点相连);newX = X ∩ N(v):排除集同步只保留 v 邻居,避免重复枚举;- 前序:R.add (v) 向下递归 ;回溯:R.remove () 撤销选择;
P.remove(v),X.add(v):v 不再参与后续同分支枚举,防止重复生成相同团。
3. 辅助工具方法
getNeighbors(v):遍历邻接矩阵,返回 v 所有相邻顶点集合;getIntersection(A,B):手动求集合交集,等价retainAll,便于理解算法数学定义。
4. 入口calc()初始化
- R=∅:初始没有选中任何点;
- P={0,1,2...n−1}:全部顶点作为初始候选;
- X=∅:初始无排除顶点。
五、算法优缺点与优化拓展
1. 原生 BK 缺点
原生无支点优化,稠密图、顶点 > 50 时递归爆炸(指数复杂度O(3n/3),NP 完全问题,无多项式解法)。
2. 经典优化:Pivot 支点优化(BK-P)
优化思路:选支点 u∈P∪X,只枚举 P-N (u) 中的顶点,大幅减少递归分支,优化后实际速度提升数倍:
// 支点优化简易改造:选支点u,遍历 P \ N(u)
Optional<Integer> pivot = P.stream().findAny();
Set<Integer> candidates = new HashSet<>(P);
if(pivot.isPresent()){
int u = pivot.get();
candidates.removeAll(getNeighbors(u));
}
for(int v : candidates){ ... }
3. 应用场景
- 社交网络社群挖掘(团 = 紧密社群);
- 图着色、蛋白质结构匹配、组合优化、电路布线。
六、极大团 vs 最大团总结
- 极大团:局部不能再加点,结果不止一个(代码 allMaximalCliques);
- 最大团:全局顶点数最多的那个极大团(代码 maximumClique);
该回溯是暴力枚举所有合法极大团,是图论团问题最基础标准实现。