我在基于最大团的图池化里提到了一点点Bron-Kerbosch算法,不过对这个算法还是没有太了解,今天单独拿出一篇文章来说这个算法。首先,https://www.jianshu.com/p/437bd6936dad 有着很详细的介绍,毕竟原论文太偏向于理论了不好读透。我会在其基础上添加一些我的理解,并解读networkx的源码。

Bron-Kerbosch算法

极大团的检索算法的时间复杂度都比较高,最近查资料发现也有许多研究旨在降低其时间复杂度,或者在稀疏的巨型图上应用,但是这些都需要对BK算法做一个深入理解。
这个算法主要是构造了三个集合,我们假设:
R集合记录的是当前极大团中已经加入的点。
P集合记录的是可能还能加入的点(也就是与R集合中所有点都有边存在的点,这样加入后,才会构成团)
X集合记录的是已经加入过某个极大团的点(作用是判重,因为会从每个结点开始,枚举所有的团,如果不对已经加入某个极大团的点,进行标记,可能会有重复的极大团出现)
伪代码如下:

1.Bron-Kerbosch Algorithm(Version 1)
2.R={}   //已确定的极大团顶点的集合
3.P={V}  //未处理顶点集,初始状态是所有结点
4.X={}   //已搜过的并且属于某个极大团的顶点集合
5.
6.BronKerbosch(R, P, X):
7.   if P and X are both empty:
8.       report R as a maximal clique
9.   for each vertex v in P:
10       BronKerbosch1(R ⋃ {v}, P ⋂ N(v), X ⋂ N(v))
11       P := P \ {v}
12       X := X ⋃ {v}

我们逐行去看。
Line3. 初始化P={V},V代表图中的所有顶点。
Line7,8. 如果P、X都为空,则说明R是最大团。因为算法的思路就是递归遍历整个点集,所以P为空的是必要条件。那么X呢?X表示已经搜索过并且属于某个最大团的点集,因为算法要找的是最大团,比如{1,2,3}这个团的子集{1,2}也是团,但是并不是最大,此时X起到记录{3}的作用,表示之前已经搜索过最大团{1,2,3},因此X此时非空表示R不是最大团。这部分可以看后文的示例,能够更清楚理解。

Line9. 对每一个顶点都进行遍历。这样势必造成计算资源的浪费。还是举例子,从{1}出发找到了最大团{1,2,3},那么从{2}还是能找到此最大团,重了。因此后续才有对其的改进。
Line10. 将{v}(注意是小v)与R合并,取P与N(v)的交集,X与N(v)的交集。N(v)表示当前节点的邻居结点。因为,最大团必须满足节点之间两两存在边,v又是最大团中的节点了,因此新加入的点的必要条件就是必须与v有边,因此必须是v的邻居。然后,在找到了v的基础上回溯。
Line11. 在对v操作完之后自然要从未处理的点集中删除。
Line12. 加入X集合代表当前状态下对包含点v的极大团已经计算完毕了。注意Line11,12都是回溯过程结束之后的操作。
下面看个例子。

T1. 初始化。此时我们记录调用的算法为BK0。
T2. v=1,传入递归函数的参数为:R={1},P={2,3},X={},N(v)={2,3},调用BK1。
T3. 此时v=2,N(v)={1,3,4},传入参数为:R={1}∪{2}={1,2},P=P∩{N(v)}={3},X={},调用BK2。
T4. 之后,到第四个小图。此时v=3,R={1,2,3},PX都为空,那么确定最大团为R={1,2,3}。调用了BK3,并且在BK3里进行了回溯。
T5. 然后回溯到BK2。在BK2传入参数之前,R={1},P={2,3},X={},注意这是在T2中传递进来的参数,而不是T3中的参数。在BK3结束之后仍需执行Line11,12,此时P={2,3}-{2}={3},X={2}。然后还是在这个BK2中,执行下一次for循环,此时v=3,N(v)={1,2},那么传递的参数为:R={1,3},P={},X={2}。我们仍把这次调用的函数成为BK3(因为是在同一层)。不过由于P为空了,函数也就结束了,但是并不返回一个最大团R={1,3},因为X并不为空。之后再回溯到BK2,但是此时BK2的for循环也已经结束了,这时候再回溯到BK1.
T6. 接下来和上面的过程是一样的了大家有兴趣自己推导吧,呼,话说递归程序真的太烧脑了。

改进

上文也提到过,在从v=1出发得到{1,2,3}已经是最大团了,算法还是对{1,2}进行了最大团的判断,因此在此基础上进行优化。
在取第一个节点v的时候,再取下一个节点放到R中,这个节点必然是v的邻居结点,因此在一开始就没有必要去继续找v的邻居,因为在for循环里一定会把v的邻居全部找到。所以直接从v的非邻居节点去搜寻极大团会减少很多不必要的计算。例如上面的程序中我们从1开始寻找极大团,找到了由1及其邻居结点构成的极大团{1、2、3},接下来我们就直接从1的非邻居结点4号结点开始寻找极大团,可以找到极大团{4、2},最终所有的极大团都被找到了:

Bron-Kerbosch Algorithm(Version 2)
R={}   //已确定的极大团顶点的集合
P={v}  //未处理顶点集,初始状态是所有结点
X={}   //已搜过的并且属于某个极大团的顶点集合1 BronKerbosch(R, P, X):
2    if P and X are both empty:
3        report R as a maximal clique
4    choose a pivot vertex u in P ⋃ X
5    for each vertex v in P \ N(u):
6        BronKerbosch1(R ⋃ {v}, P ⋂ N(v), X ⋂ N(v))
7        P := P \ {v}
8        X := X ⋃ {v}

相比于上一个版本,只是稍微修改。
Line4. 表示从P∪X中选择节点,然后找P\N(u)找到u的非邻居节点,然后根据这非邻居节点进行极大团的查找。
然后是两个不同版本的对比;

Networkx代码

源码链接:https://networkx.github.io/documentation/stable/_modules/networkx/algorithms/clique.html#find_cliques

# 寻找图中所有最大团,通过迭代方式实现(相比递归空间复杂度更低一些),输入是networktx的内部类graph。
def find_cliques(G):if len(G) == 0:return# 寻找节点u以及其的所有邻居节点的邻接表,这个是为了后续处理方便adj = {u: {v for v in G[u] if v != u} for u in G}Q = [None]      # 伪代码中的集合Rsubg = set(G)cand = set(G)   # 伪代码中的P集合# u是在G中,相邻节点最多的点。max是根据key寻找最大的结果返回,此处key通过匿名函数lambda定义,# u是参数,也就是遍历集合中的点,返回值是len(cand&adj[u]),就是与邻居集合与cand集合交集的大小。# 本句话的意思是:返回邻居集合与cand集合的交集最大的节点u。其实这目的就是选出一个结点,对应伪代码中的Line4u = max(subg, key=lambda u: len(cand & adj[u]))ext_u = cand - adj[u]    # 差集,对应伪代码Line5中的P \ N(u)stack = []try:   # 接下来是一个死循环,通过except抛出错误跳出这个循环while True:if ext_u:   # P \ N(u),也就是还存在可以扩展的候选的点q = ext_u.pop()   # 弹出一个点进行最大团的搜索并入操作cand.remove(q)    # 要从cand(P)中移除这个点,相当于伪代码中的for循环了Q[-1] = q    # 将刚刚弹出的节点放入Q中,也就是为团新增节点了,对应R ⋃ {v}adj_q = adj[q]    # 找q所有的邻居subg_q = subg & adj_q   # 取交集,对应 P ⋂ N(v)# 也就是当subg_q为空的时候。if not subg_q:    # 也就是sub_q为空,这个sub_qduiying对应集合Xyield Q[:]    # 每次yield都返回一个Q的生成器,这个Q就是最大团啦else:cand_q = cand & adj_q  # X ⋂ N(v)if cand_q:stack.append((subg, cand, ext_u))   # 入栈,相当于递归过程中的参数传递,只不过给用显示的堆栈存起来了Q.append(None)subg = subg_q   # 以下四步都相当于参数更新cand = cand_qu = max(subg, key=lambda u: len(cand & adj[u]))ext_u = cand - adj[u]  # 更新完毕则执行下一个循环,相当于又执行了一次BronKerbosch函数else:   # 当前状况下不存在可拓展的点了Q.pop()   # 最大团出栈subg, cand, ext_u = stack.pop()    # 参数出栈# 这是一个索引错误,其报错源头在实际测试中是subg, cand, ext_u = stack.pop()# 也就是我堆栈中不存在任何参数了,这代表所有的回溯过程的结束except IndexError:  pass

这个是networkx中的回溯版本,相比于递归效率更高。但是在实际的操作中,如果图比较小,递归和回溯之间的时间差别都不大的。递归的版本如下:

    def find_cliques_recursion(self,R, P, X):'''a Recursion version of finding all max cliques in graph'''# print(P)if len(self.G) == 0:returnif not P and not X:self.Cliques.append(R)return'''Fundamental study: Enumerating all connected maximal common subgraphs in two graphsuse max degree for node selection'''u = max(P|X, key=lambda u: len(P & self.adj[u]))for v in (P - self.adj[u]):self.find_cliques_recursion(R|set([v]), P&self.adj[v], X&self.adj[v])P = P - set([v])X.add(v)

对于一些巨型的稀疏图,就得采用一些启发式的方法从根本上降低时间复杂度了。关于最大团的算法,有一篇review写得比较全面:A review on algorithms for maximum clique problems,大家感兴趣的可以自己看一看。
其中yield的具体解释可以看我之前的博客:https://blog.csdn.net/qq_36618444/article/details/107486004

极大团与networkx Bron-Kerbosch算法源码解读相关推荐

  1. 1、SSD算法源码解读-如何进行数据增强

    文章目录 目标检测任务中数据包含的基本信息 两种类型的数据增强 Int类型图片转为float类型 去均值 将x,y,w,h转化从0-1的相对值转化为图片真实值 将x,y,w,h转化真实值转化为0-1的 ...

  2. 基于新唐M0的XXTEA加密解密算法源码

    源:基于新唐M0的XXTEA加密解密算法源码 /*--------------------------------------------------------------------------- ...

  3. [转] GIS算法源码集合

    其他GIS相关代码下载索引 http://www.mygis.com.cn/codeindex10.htm 1.深度优先实现的路径分析源码 http://www.mygis.com.cn/codes/ ...

  4. 机器学习算法源码全解析(三)-范数规则化之核范数与规则项参数选择

    前言 参见上一篇博文,我们聊到了L0,L1和L2范数,这篇我们絮叨絮叨下核范数和规则项参数选择.知识有限,以下都是我一些浅显的看法,如果理解存在错误,希望大家不吝指正.谢谢. 机器学习算法源码全解析( ...

  5. Learning to Rank中Pointwise关于PRank算法源码实现

    [学习排序] Learning to Rank中Pointwise关于PRank算法源码实现 标签: 学习排序PRankPointwiseLearning to Rank代码实现 2015-01-28 ...

  6. 超像素SLIC算法源码阅读

    超像素SLIC算法源码阅读 超像素SLIC算法源码阅读 SLIC简介 源码阅读 实验结果 其他超像素算法对比 超像素SLIC算法源码阅读 SLIC简介 SLIC的全称Simple Linear Ite ...

  7. diff算法_vue源码解读 diff算法

    导语 最近碰到部分业务场景,代码逻辑需要了解"数组变更后,具体变更了哪一些元素,以及变更的位置..".于是仔细研究并覆写了一遍针对数组变化的diff算法,在这里做下diff算法的逻 ...

  8. Java自动计算迷宫正确路线算法源码

    简介: Java自动计算迷宫正确路线算法源码,首先迷宫需要满足存在开始标识和结束标识与墙标识,然后设置好行数与列数就可以开始计算正确路线了,采用的是为二维数组然后走遍所有路线的方式. 网盘下载地址: ...

  9. java计算机毕业设计ssm社区团购系统13kbd(附源码、数据库)

    java计算机毕业设计ssm社区团购系统13kbd(附源码.数据库) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(Webstorm也行)+ Ec ...

  10. Metis异常检测算法源码概要

    Metis异常检测算法源码概要 算法源码目录 算法层目录 基于指数移动平均算法(EWMA) 孤立森林 xgboost gbdt 3-Sigma 多项式回归 特征层目录 拟合特征 分类特征 统计特征 其 ...

最新文章

  1. UVA 10515 - Powers Et Al.(数论)
  2. AI智能体学会动物进化法则:李飞飞等提出深度进化RL
  3. 分布式服务框架 dubbo/dubbox 入门示例(转)
  4. python窗口程序-python操作Windows窗口程序
  5. Ajax/REST 第1部分
  6. JVM调优总结(七)-典型配置举例1
  7. pytorchyolov4训练_使用pytorch-yolov5 訓練自己的數據集-2020.6.15
  8. 嵌入式工作笔记0001---认识SoC
  9. 10BASE-2 是什么意思
  10. python什么是交换算法_确定交换的算法
  11. ACM金牌选手算法讲解《线性表》
  12. OpenCV学习笔记——图像平滑处理
  13. c语言中除号用百分号,【期末复习】人教版六年级数学(下册)知识要点
  14. matlab代码:考虑实时市场联动的电力零售商鲁棒定价策略
  15. 已知接入Internet的计算机用户名,计算机一级笔试试题及答案(1)
  16. 微信JSSDK使用签名算法
  17. C语言中的清屏函数(自己编写)
  18. 关键词挖掘的方法和技巧
  19. 实验.........
  20. 【游戏开发实战】(完结)使用Unity制作像天天酷跑一样的跑酷游戏——第七篇:游戏界面的基础UI

热门文章

  1. 利用C#进行CAD二次开发
  2. 什么是URL?URL是什么意思?
  3. python科学计算的例子_Python科学计算:NumPy
  4. JavaSE实战项目:飞翔的小鸟(完整版)
  5. html alt 作用,alt标签是什么意思,alt标签的作用及优化
  6. 新浪微博热门话题(字符串处理)
  7. windos系统如何获得超级管理员权限
  8. 三星手机服务器无影响,终于找到手机网速慢的原因了!原来有这么多讲究
  9. 电脑鼠标双击桌面图标打不开怎么办
  10. python太阳代码_用86行Python代码模拟太阳系