R := {}P := node set of G X := {}BronKerbosch1(R, P, X): if P and X are both empty: report R as a maximal clique for each vertex v in P: BronKerbosch1(R ⋃ {v}, P ⋂ N(v), X ⋂ N(v)) P := P \ {v} X := X ⋃ {v}
C++ 实现
实现代码
#include <cstring>#include <iostream>using namespace std;constexpr int MAXN = 105;struct MaxClique { bool g[MAXN][MAXN]; int n, dp[MAXN], st[MAXN][MAXN], ans; // dp[i]表示第i个点之后能组成的最大团的大小, // st[i][j]表示算法中第i层dfs所需要的点的集合,保存有可能是最大团其中之一的点 void init(int n) { this->n = n; memset(g, false, sizeof(g)); } void addedge(int u, int v, int w) { g[u][v] = w; } bool dfs(int sz, int num) { if (sz == 0) { if (num > ans) { ans = num; return true; } return false; } for (int i = 0; i < sz; i++) { // 在第num层的集合中枚举一个点i if (sz - i + num <= ans) return false; // 剪枝1 int u = st[num][i]; if (dp[u] + num <= ans) return false; // 剪枝2 int cnt = 0; for ( int j = i + 1; j < sz; j++) { // 在第num层遍历在i之后的且与i所相连的点,并且加入第num+1层集合 if (g[u][st[num][j]]) st[num + 1][cnt++] = st[num][j]; } if (dfs(cnt, num + 1)) return true; } return false; } int solver() { ans = 0; memset(dp, 0, sizeof(dp)); for (int i = n; i >= 1; i--) { int cnt = 0; for (int j = i + 1; j <= n; j++) { // 初始化第1层集合 if (g[i][j]) st[1][cnt++] = j; } dfs(cnt, 1); dp[i] = ans; } return ans; }} maxclique;int main() { cin.tie(nullptr)->sync_with_stdio(false); int n; while (cin >> n, n) { maxclique.init(n); for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { int x; cin >> x; maxclique.addedge(i, j, x); } } cout << maxclique.solver() << '\n'; } return 0;}
BronKerbosch(All, Some, None): if Some and None are both empty: report All as a maximal clique // 所有点已选完,且没有不能选的点,累加答案 for each vertex v in Some: // 枚举 Some 中的每一个元素 BronKerbosch1(All ⋃ {v}, Some ⋂ N(v), None ⋂ N(v)) // 将 v 加入 All,显然只有与 v 为朋友的人才能作为备选,None 中也只有与 v 为朋友的才会对接下来造成影响 Some := Some - {v} // 已经搜过,从 Some 中删除,加入 None None := None ⋃ {v}
为了节省时间和让算法更快的回溯,我们可以通过设定关键点(pivot vertex)v 进行优化。
我们知道在上述的算法中必然有许多重复计算之前计算过的极大团,然后回溯的过程。
以前文提到的 R、P、X 三个集合为例:
我们考虑如下问题,取集合 P∪X 中的一个点 u,要与 R 集合构成极大团,那么取的点必然是 P∩N(u) 中一个点(N(u) 代表与 u 相邻的点)。
如果取完 u 之后我们再取与 u 相邻的点 v 也能加入到极大团,那么我们只取 u 就好了。这样做可以减少之后对 v 的重复计算。我们之后只需要取与 u 不相邻的点。
加入优化后的 C++ 代码实现:
实现代码
#include <cstring>#include <iostream>using namespace std;constexpr int MAXN = 130;bool mp[MAXN][MAXN];int some[MAXN][MAXN], none[MAXN][MAXN], all[MAXN][MAXN];int n, m, ans;void dfs(int d, int an, int sn, int nn) { if (!sn && !nn) ++ans; int u = some[d][0]; for (int i = 0; i < sn; ++i) { int v = some[d][i]; if (mp[u][v]) continue; for (int j = 0; j < an; ++j) all[d + 1][j] = all[d][j]; all[d + 1][an] = v; int tsn = 0, tnn = 0; for (int j = 0; j < sn; ++j) if (mp[v][some[d][j]]) some[d + 1][tsn++] = some[d][j]; for (int j = 0; j < nn; ++j) if (mp[v][none[d][j]]) none[d + 1][tnn++] = none[d][j]; dfs(d + 1, an + 1, tsn, tnn); some[d][i] = 0, none[d][nn++] = v; if (ans > 1000) return; }}int work() { ans = 0; for (int i = 0; i < n; ++i) some[1][i] = i + 1; dfs(1, 0, n, 0); return ans;}int main() { cin.tie(nullptr)->sync_with_stdio(false); while (cin >> n >> m) { memset(mp, 0, sizeof mp); for (int i = 1; i <= m; ++i) { int u, v; cin >> u >> v; mp[u][v] = mp[v][u] = true; } int tmp = work(); if (tmp > 1000) cout << "Too many maximal sets of friends.\n"; else cout << tmp << '\n'; } return 0;}