BK(Bron-Kerbosch)算法是一种递归方法,用于找到图中所有最大团(也叫Maximal Clique)。最大团是一组完全相连的节点,并且无法再添加节点而保持完全连接。BK算法通过扩展当前团来找到更大的团,确保不会重复记录同一个团(Clique)。
BK算法的步骤
-
三个集合:
U:当前的团Clique(或者说候选团),正在扩展。C:可以加入到团中的候选节点集合。NOT:已经被排除的节点集合,不再扩展这些节点。
-
算法流程:
- 每次递归调用检查候选集
C和禁止集NOT是否为空。 - 当
C和NOT都为空时,说明U是一个最大团,将其记录下来。 - 否则,从
C中选择一个节点加入到U中形成一个新的团,更新C和NOT为与该节点相连的节点子集,并继续递归。 - 每次选择一个节点扩展时,将其从
C移到NOT中,确保不会重复计算包含该节点的团。
- 每次递归调用检查候选集
伪代码
Algorithm 1: BK(U, C, NOT)
if C = ∅ then
if NOT = ∅ then
Output G[U] as a maximal clique;
return;
while C ≠ ∅ do
if ∃v ∈ NOT, N(v) ⊇ C then
return;
Select a node u randomly from C;
C ← C \ u;
Cnew ← C ∩ N(u);
NOTnew ← NOT ∩ N(u);
BK(U ∪ {u}, Cnew, NOTnew);
NOT ← NOT ∪ {u};
-
输入:
U表示当前团的节点,C表示候选节点集合,NOT表示已经检查过的节点集合。 -
步骤:
-
如果
C为空且NOT也为空,则输出G[U]作为最大团。 -
对于
C中的每个节点u,执行以下步骤:- 检查
NOT中是否有节点v满足其邻居包含C的所有节点,如果有则直接返回(剪枝条件)。 - 随机选择
C中的一个节点u,从C中移除。 - 计算新的候选节点
Cnew和NOT集合NOTnew,仅保留邻居节点。 - 递归调用
BK(U ∪ {u}, Cnew, NOTnew)。 - 将节点
u加入到NOT中。
- 检查
-
算法演示
采用下面的图片进行展示:
- 迭代第一步,这里选取B作为第一个节点
- 迭代第二步,回溯然后到B-D的,A因为在第一步中已经使用过了,其实也加入到了NOT中,并且每个用过的结点都会在C中remove掉,避免重复使用,所以在原本的C中只剩下{'D','C'}
- 第三步,此时因为A,D已经使用过,并且在C中已经被删除只剩下结点C,添加后此时候选集C中为空,但是因为Not中有{A,D}结点,不满足C和NOT为空的条件,所以不会添加到cliques中。
- 继续递归,(步骤模拟可以去跑一下Python 代码)最终找到Cliques如下:
Python实现
以下是用Python实现的BK算法,找出图中所有最大团:
def bron_kerbosch(U, C, NOT, graph, cliques):
# 如果C和NOT都为空,U是一个最大团
if not C and not NOT:
cliques.append(U)
return
# 遍历候选集C中的节点
for v in list(C):
# 递归调用,将v加入U扩展成新的团
bron_kerbosch(U | {v},
C & graph[v],
NOT & graph[v],
graph,
cliques)
# 更新C和NOT集合,确保不会重复选择节点v
C.remove(v)
NOT.add(v)
def find_maximal_cliques(graph):
# 初始化集合和结果存储
cliques = []
nodes = set(graph.keys())
bron_kerbosch(set(), nodes, set(), graph, cliques)
return cliques
# 定义一个无向图,图的每个节点对应相连节点的集合
graph = {
'A': {'B', 'C'},
'B': {'A', 'C', 'D'},
'C': {'A', 'B', 'D'},
'D': {'B', 'C', 'E'},
'E': {'D'}
}
# 找到所有最大团
maximal_cliques = find_maximal_cliques(graph)
print("Maximal Cliques:", maximal_cliques)
代码说明
bron_kerbosch函数是主递归部分,它通过扩展集合U来找到最大团。C & graph[v]和NOT & graph[v]表示仅选择与v相连的节点,这样可以确保扩展出的U保持为一个完全子图。find_maximal_cliques函数负责初始化并调用递归函数来找到图中所有最大团。
U | {v}: 表示将节点v添加到集合U。U是当前正在构建的团,也就是在递归时暂时包含的节点。C & graph[v]: 表示取集合C和graph[v]的交集。C是候选集合,包含可以扩展U的节点,而graph[v]是与节点v相邻的所有节点。交集C & graph[v]确保了该候补节点与前面的节点和新添加的结点都是相邻的,确保新添加的结点后还是一个完全图。NOT & graph[v]: 表示取集合NOT和graph[v]的交集。NOT是被排除的节点集合,包含不再参与当前团扩展的节点。graph[v]同样是与节点v邻接的节点,交集NOT & graph[v]表示从排除集合中筛选出与v邻接的节点,作为递归调用中新排除的集合graph: 是图结构,通常为一个邻接表或邻接矩阵,用来表示每个节点的邻接关系。cliques: 是最终结果的集合列表,用来存储找到的所有最大团。
整体逻辑
这段调用会递归执行 Bron-Kerbosch 算法,通过递归扩展集合 U 来查找所有最大团。具体流程是:
- 尝试将每个候选节点
v加入到当前团U。 - 然后更新候选集合
C和排除集合NOT以限制进一步的扩展,确保新团依然是一个团。 - 如果满足条件的团无法继续扩展,它就是一个最大团,记录到
cliques中。
运行结果
对于定义的简单无向图,代码会输出所有的Maximal-Clique。
Reference
- C. Bron and J. Kerbosch. Algorithm 457: finding all cliques of an undirected graph. Communications of the ACM, 16(9):575–577, 1973