Bron-Kerbosh算法求解极大团

Bron-Kerbosh算法求解极大团

参考资料:https://www.jianshu.com/p/437bd6936dad

这篇文章讲得很好,本文的代码也是参照这篇文章,用python实现。

什么是极大团?

团、极大团、最大团的定义请问这篇文章:https://www.jianshu.com/p/dabbc78471d7

为什么会用到极大团?

了解到极大团这个知识,是在构建任务指派模型的时候接触到的。在对任务进行指派时,每个任务都有其开始时间、结束时间,在不考虑员工资质的情况下,任务之间可能会存在时间上的冲突,如下图所示。这幅图中存在哪些极大团
呢?

在这里插入图片描述
在这里插入图片描述

根据极大团的原理:如果一个团不被其他任一团所包含,即它不是其他任一团的真子集,则称该团为图G的极大团(maximal clique)。

怎么用到模型上?

  1. 回到上面两幅图,需要对第一幅图中的5个任务进行人员指派,应该需要以下约束条件:

当两任务存在时间冲突时:
x i , k + x j , k ≤ 1 , i , j ∈ N , k ∈ K x_{i,k}+x_{j,k}\le 1 , i,j\in N, k\in K xi,k+xj,k1,i,jN,kK
其中, i , j i,j i,j是任务的序号, k k k是员工的序号。

展开看(这里先不管员工):
x 1 + x 2 ≤ 1   ,   x 1 + x 3 ≤ 1   ,   x 1 + x 4 ≤ 1   ,   x 2 + x 3 ≤ 1   , x 2 + x 4 ≤ 1   ,   x 3 + x 4 ≤ 1   ,   x 3 + x 5 ≤ 1   ,   x 4 + x 5 ≤ 1 x_1 + x_2 \le 1 \ , \ x_1 + x_3 \le 1 \ , \ x_1 + x_4 \le 1 \ , \ x_2 + x_3 \le 1 \ , \\x_2 + x_4 \le 1 \ , \ x_3 + x_4 \le 1 \ , \ x_3 + x_5 \le 1 \ , \ x_4 + x_5 \le 1 x1+x21 , x1+x31 , x1+x41 , x2+x31 ,x2+x41 , x3+x41 , x3+x51 , x4+x51

  1. 用极大团构建约束,第二幅图列出了5个任务的所有极大团,因为极大团中所包含的任务都是存在时间冲突的,所以在一个团中,只能有一个为1。

c l i q u e 1 , 2 , 3 , 4 → x 1 + x 2 + x 3 + x 4 ≤ 1 c l i q u e 3 , 4 , 5 → x 3 + x 4 + x 5 ≤ 1 clique{1,2,3,4} \to x_1 + x_2 + x_3 +x_4 \le 1 \\ clique{3,4,5} \to x_3 + x_4 + x_5 \le 1 clique1,2,3,4x1+x2+x3+x41clique3,4,5x3+x4+x51

从上面的两种约束可以看出,使用极大团构建约束,约束的数量将会大大减少,特别是当任务的数量更大的时候。

现在就是如何找到极大团。

Bron-Kerbosch算法

算法的思路、过程等,我建议你看这篇文章:极大团(maximal clique)算法:Bron-Kerbosch算法

下面给出用python编写求解极大团的代码。

首先这是未改进版本的BK算法,以下也给出了几个小算例。

import copy
import time
import pandas as pd
import numpy as np


# BronKerbosch,cnt记录目前是第几层
def BronKerbosch(d, rn, pn, xn):
    # 判断P、X是否为空,为空则找到最大值
    if pn == 0 and xn == 0:
        route.append(copy.deepcopy(R[d]))
    # 遍历P中的每一个v,len(P)==0时,搜索到终点
    for j in range(pn):
        # 取出P中的第j个点
        v = P[d][j]
        R[d+1] = []     # 因为后面是直接在list后面添加元素,所以先将下一层的list清空
        for k in range(rn):
            R[d+1].append(R[d][k])
        R[d+1].append(v)    # 将v节点,添加到R集合中

        # 用来分别记录下一层中P集合和X集合中节点的个数
        tp, tx = 0, 0
        # 更新X集合(下一层X集合),保证X集合中的点都能与R集合中所有的点相连接
        X[d + 1] = []   # 因为后面是直接在list后面添加元素,所以先将下一层的list清空
        for k in X[d]:
            if conf[v][k]:
                X[d + 1].append(k)
                tx += 1
        # 更新P集合时,同时以R、X两个集合作为依据,在X中的元素不能出现在P中
        P[d+1] = []     # 因为后面是直接在list后面添加元素,所以先将下一层的list清空
        for k in P[d]:
            if conf[v][k] and k not in X[d]:
                P[d+1].append(k)
                tp += 1

        # 递归进入下一层
        BronKerbosch(d+1, rn+1, tp, tx)
        # 完成后,将操作的节点,放入X中,开始下面的寻找
        X[d].append(v)
        xn += 1


# 任务冲突矩阵
def conflict(data):
    m = len(data)
    M = [[0]*m]*m
    M = np.array(M)
    for k in range(m):
        for j in range(k+1,m):
            if data.taskStartTime[j] <= data.taskStartTime[k] <= data.taskEndTime[j] \
                    or data.taskStartTime[j] <= data.taskEndTime[k] <= data.taskEndTime[j]:
                M[j,k] = 1
                M[k,j] = 1
    return M


# 获取冲突矩阵
st = time.time()
data = pd.read_excel('data.xlsx')
data.taskStartTime = pd.to_datetime(data.taskStartTime)
data.taskEndTime = pd.to_datetime(data.taskEndTime)
conf = conflict(data)
# conf = [[0, 1, 1, 0], [1, 0, 1, 1], [1, 1, 0, 0], [0, 1, 0, 0]]
# conf = [[0, 1, 0, 1, 1], [1, 0, 1, 0, 1], [0, 1, 0, 0, 1], [1, 0, 0, 0, 1], [1, 1, 1, 1, 0]]
# conf = [[0,1,1,1,0,0,0],[1,0,1,1,1,0,0],[1,1,0,1,0,0,0],[1,1,1,0,1,1,0],[0,1,0,1,0,0,1],[0,0,0,1,0,0,0],[0,0,0,0,1,0,0]]
# 节点总数
n = len(conf)
# 定义三个集合,R已确定的极大团顶点的集合,P未处理顶点集,X以搜过的并且属于某个极大团的顶点集合
R, P, X = [[]]*n, [[]]*n, [[]]*n
P[0] = [i for i in range(n)]    # 初始化未处理集合
route = []
BronKerbosch(0, 0, len(conf), 0)
print(len(route))
for key in route:
    print(key)
print(time.time() - st)

下面是经过改进的代码,改进的原理也请看上面那篇文章。

import copy
import time
import pandas as pd
import numpy as np


# BronKerbosch,cnt记录目前是第几层
def BronKerbosch(d, rn, pn, xn, u):
    # 判断P、X是否为空,为空则找到最大值
    if pn == 0 and xn == 0:
        route.append(copy.deepcopy(R[d]))
    if len(P[d]) > 0:
        u = P[d][0]  # 记录最近放入P集合中的元素
    # 遍历P中的每一个v,len(P)==0时,搜索到终点
    for j in range(pn):
        # 取出P中的第j个点
        v = P[d][j]
        # 判断u,v是否是邻居,是则跳过
        if u != -1 and conf[u][v] == 1: continue
        R[d + 1] = []  # 因为后面是直接在list后面添加元素,所以先将下一层的list清空
        for k in range(rn):
            R[d + 1].append(R[d][k])
        R[d + 1].append(v)  # 将v节点,添加到R集合中

        # 用来分别记录下一层中P集合和X集合中节点的个数
        tp, tx = 0, 0
        # 更新X集合(下一层X集合),保证X集合中的点都能与R集合中所有的点相连接
        X[d + 1] = []  # 因为后面是直接在list后面添加元素,所以先将下一层的list清空
        for k in X[d]:
            if conf[v][k]:
                X[d + 1].append(k)
                tx += 1
        # 更新P集合时,同时以R、X两个集合作为依据,在X中的元素不能出现在P中
        P[d + 1] = []  # 因为后面是直接在list后面添加元素,所以先将下一层的list清空
        for k in P[d]:
            if conf[v][k] and k not in X[d]:
                P[d + 1].append(k)
                tp += 1

        # 递归进入下一层
        BronKerbosch(d + 1, rn + 1, tp, tx, u)
        # 完成后,将操作的节点,放入X中,开始下面的寻找
        X[d].append(v)
        xn += 1
        # print("==========")


# 任务冲突矩阵
def conflict(data):
    m = len(data)
    M = [[0]*m]*m
    M = np.array(M)
    for k in range(m):
        for j in range(k+1,m):
            if data.taskStartTime[j] <= data.taskStartTime[k] <= data.taskEndTime[j] \
                    or data.taskStartTime[j] <= data.taskEndTime[k] <= data.taskEndTime[j]:
                M[j,k] = 1
                M[k,j] = 1
    return M


# 获取冲突矩阵
st = time.time()
data = pd.read_excel('data.xlsx')
data.taskStartTime = pd.to_datetime(data.taskStartTime)
data.taskEndTime = pd.to_datetime(data.taskEndTime)
conf = conflict(data)
# conf = [[0, 1, 1, 0], [1, 0, 1, 1], [1, 1, 0, 0], [0, 1, 0, 0]]
# conf = [[0, 1, 0, 1, 1], [1, 0, 1, 0, 1], [0, 1, 0, 0, 1], [1, 0, 0, 0, 1], [1, 1, 1, 1, 0]]
# conf = [[0, 1, 1, 1, 0, 0, 0], [1, 0, 1, 1, 1, 0, 0], [1, 1, 0, 1, 0, 0, 0], [1, 1, 1, 0, 1, 1, 0],
#         [0, 1, 0, 1, 0, 0, 1], [0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0]]
# 节点总数
n = len(conf)
# 每个节点的边数
node_sum = []
for i in range(n):
    node_sum.append(sum(conf[i]))
# 定义三个集合,R已确定的极大团顶点的集合,P未处理顶点集,X以搜过的并且属于某个极大团的顶点集合
R, P, X = [[]] * n, [[]] * n, [[]] * n
# 初始化,以边数做倒序
P[0] = sorted(range(len(node_sum)), key=lambda k: node_sum[k], reverse=True)
route = []
BronKerbosch(0, 0, len(conf), 0, -1)
print(len(route))
for key in route:
    print(key)
print(time.time() - st)

任务数较小时,改进前后的求解时间上没有很大的差异,这里我用了一份其他的数据进行测试,任务数70多个,改进前求解需要24秒左右,改进后仅需0.4秒。

(本文为学习记录用)

代码中的数据:

链接:https://pan.baidu.com/s/1zHq-CQs3kts_Nfj4C_XM2Q
提取码:e5dq

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值