BK算法(Bron-Kerbosch Algorithm)

511 阅读5分钟

BK(Bron-Kerbosch)算法是一种递归方法,用于找到图中所有最大团(也叫Maximal Clique)。最大团是一组完全相连的节点,并且无法再添加节点而保持完全连接。BK算法通过扩展当前团来找到更大的团,确保不会重复记录同一个团(Clique)。

BK算法的步骤

  1. 三个集合

    • U:当前的团Clique(或者说候选团),正在扩展。
    • C:可以加入到团中的候选节点集合。
    • NOT:已经被排除的节点集合,不再扩展这些节点。
  2. 算法流程

    • 每次递归调用检查候选集 C 和禁止集 NOT 是否为空。
    • CNOT 都为空时,说明 U 是一个最大团,将其记录下来。
    • 否则,从 C 中选择一个节点加入到 U 中形成一个新的团,更新 CNOT 为与该节点相连的节点子集,并继续递归。
    • 每次选择一个节点扩展时,将其从 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表示已经检查过的节点集合。

  • 步骤

    1. 如果C为空且NOT也为空,则输出G[U]作为最大团。

    2. 对于C中的每个节点u,执行以下步骤:

      • 检查NOT中是否有节点v满足其邻居包含C的所有节点,如果有则直接返回(剪枝条件)。
      • 随机选择C中的一个节点u,从C中移除。
      • 计算新的候选节点CnewNOT集合NOTnew,仅保留邻居节点。
      • 递归调用BK(U ∪ {u}, Cnew, NOTnew)
      • 将节点u加入到NOT中。

算法演示

采用下面的图片进行展示:

image.png

  • 迭代第一步,这里选取B作为第一个节点

image.png

  • 迭代第二步,回溯然后到B-D的,A因为在第一步中已经使用过了,其实也加入到了NOT中,并且每个用过的结点都会在C中remove掉,避免重复使用,所以在原本的C中只剩下{'D','C'}

image.png

  • 第三步,此时因为A,D已经使用过,并且在C中已经被删除只剩下结点C,添加后此时候选集C中为空,但是因为Not中有{A,D}结点,不满足C和NOT为空的条件,所以不会添加到cliques中。

image.png

  • 继续递归,(步骤模拟可以去跑一下Python 代码)最终找到Cliques如下:

image.png

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 函数负责初始化并调用递归函数来找到图中所有最大团。
  1. U | {v} : 表示将节点 v 添加到集合 UU 是当前正在构建的团,也就是在递归时暂时包含的节点。
  2. C & graph[v] : 表示取集合 Cgraph[v] 的交集。C 是候选集合,包含可以扩展 U 的节点,而 graph[v] 是与节点 v 相邻的所有节点。交集 C & graph[v] 确保了该候补节点与前面的节点和新添加的结点都是相邻的,确保新添加的结点后还是一个完全图。
  3. NOT & graph[v] : 表示取集合 NOTgraph[v] 的交集。NOT 是被排除的节点集合,包含不再参与当前团扩展的节点。graph[v] 同样是与节点 v 邻接的节点,交集 NOT & graph[v] 表示从排除集合中筛选出与 v 邻接的节点,作为递归调用中新排除的集合
  4. graph: 是图结构,通常为一个邻接表或邻接矩阵,用来表示每个节点的邻接关系。
  5. 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