您的位置:首页 > 编程语言 > Python开发

类动态规划求解较小规模的最大团问题(Python实现)

2016-12-22 18:56 886 查看
1.图:由点、边(点与点之间连线),组成的集合,如点集V=[0,1,2,3,4],边集E=[[1,3,4],[2,3,4],[4],[4],[]],则(V,E)就是一个图,其表达的意思如下:



该图中含有5个端点,分别为0,1,2,3,4,这些点存在V中,如端点1对应V[1]=1端点之间会连着线,称为边,如1和2之间连的边,就对应E中E[1]=[2,3,4]的元素2,如0和4之间连的边就对应E[0]=[1,3,4]含有元素4(E[4]=[]不含元素0是因为0<4,只需在E[0]中记录了4即可)

2.团:一个图可能会有多个团,团是V的子集,记为集合G,且保证G中任意两点之间都有连线如G=[1,3,4],其中1,3,4三点两两相连,一个图的包含元素最多的团称为最大团。

最大团问题网上的求解办法非常多(本身其实这是一个NP问题),这次比较特殊的地方是要求使用Python,而且如果有多个解要输出多个具体的解(也就是说中间结果要记录),总之题目描述就是:

输入:V,E

输出:最大团数目,最大团集合(如果有多个要求全部输出)

采用动态规划的思想就是将问题分段求解,可以将问题分为5段:求含有一个、两个、三个、四个、五个元素的团,其中含有N元素的团必包含含有N-1个元素的团,故只需在上一段问题的答案的基础上,尝试给团添加新的元素。

另外,实际上例如求解出两个元素的团,如本题中的[0,1],[0,3],[0,4],[1,2],[1,3],[1,4],[2,4],[3,4]时,已经裁掉了[0,2],[2,3]的下面的解了,所以也算有一点剪枝的意思。接下来直接看代码实现:
#-*- coding=utf-8 -*-

V=[0,1,2,3,4]
# E=[[1,3,4],[2,4],[4],[4],[]]
E=[[1,3,4],[2,3,4],[4],[4],[]]

###较小规模的最大团问题
import copy

def isConnected(u,v):
if u==-1 or v==-1:###虚拟节点-1与所有的节点都相连
return 1
edge_points=E[u]
if v in edge_points:
return 1
else:
edge_points = E[v]
if u in edge_points:
return 1
else:
return 0

def isConnectedAll(clique,v):#判断v是否和clique中所有节点相连
flag = 1
for i in clique:
if not isConnected(i,v):
flag =0
break
return flag

class Step:
def __init__(self):
self.maxClique = [] #计算完毕时的解集(每个阶段的实际结果)
self.cliqueList = []#计算时用的解集
self.maxnC = 0
def maxCliqn(self):#计算当前阶段最大值
max = 0
for clique in self.cliqueList:
if max < len(clique):
max = len(clique)
return max
def isNew(self,clique): #判断一个解组合是否已经存在于该阶段的实际解集中
for cl in self.maxClique:#针对每个已存入的解集进行判断
diff = list(set(clique).difference(set(cl)))  # 取解的差集
if (len(diff)):
continue      #差集不为空,说明不同,继续循环
else:
return False  # 差集为空,说明有个解完全一样,返回False

return True

def updateMaxClique(self):#更新当前阶段的最大团数目
self.maxnC= self.maxCliqn()
for clique in self.cliqueList:
if(len(clique)==self.maxnC):
if self.isNew(clique):
self.maxClique.append(clique)

if __name__ == "__main__":
n = len(V)
solutions = {}
for i in range(0,n):
solutions[i]= Step()    #初始化n个阶段
for v in V:
a = []
a.append(v)
solutions[0].cliqueList.append(a)
solutions[0].updateMaxClique()#设置初始值

for i in range(1,n):
#cliqList= solutions[i-1].maxClique
preData = solutions[i-1]
cliqList = preData.maxClique
preMax = preData.maxnC
for clique in cliqList:#针对前一阶段的每个clique求解
for v in V:#针对所有的点
tempclique = copy.deepcopy(clique)##必须使用深拷贝
if not v in tempclique:#如果该clique没有包含v
if isConnectedAll(tempclique,v):#如果v与clique的所有点相连
tempclique.append(v)#加入该点
solutions[i].cliqueList.append(tempclique)#加入这个解
solutions[i].updateMaxClique()
if not len(solutions[i].maxClique):#如果已经找不到更多的点加入团,那么后面的也不用计算了(比如找不到4个的团,那么5个的团也没必要再尝试计算)
break

for i in range(0,n):
print("step"+str(i)+": "+str(solutions[i].maxClique))

for i in range(n-1,-1,-1):
solution = solutions[i]
if len(solution.maxClique):
maxn = solution.maxnC
print("最大团数目是"+ str(maxn)+"个")
print("最大团为:")
print(solution.maxClique)
break
值得注意的一点是,python里面直接用赋值符号得到的对象实例是使用的浅拷贝,跟java类似,所以要使用copy模块的deepcopy函数来建立临时列表做各种判断以及修改添加(因为我也对python不太熟悉,而且原来没仔细考虑过拷贝引用带来的问题,中间错了好一阵)。

输出:

V=[0,1,2,3,4]
E=[[1,3,4],[2,4],[4],[4],[]]


step0: [[0], [1], [2], [3], [4]]

step1: [[0, 1], [0, 3], [0, 4], [1, 2], [1, 4], [2, 4], [3, 4]]

step2: [[0, 1, 4], [0, 3, 4], [1, 2, 4]]

step3: []

step4: []

最大团数目是3个

最大团为:

[[0, 1, 4], [0, 3, 4], [1, 2, 4]]

E=[[1,3,4],[2,3,4],[3,4],[4],[]]

step0: [[0], [1], [2], [3], [4]]
step1: [[0, 1], [0, 3], [0, 4], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
step2: [[0, 1, 3], [0, 1, 4], [0, 3, 4], [1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]
step3: [[0, 1, 3, 4], [1, 2, 3, 4]]
step4: []
最大团数目是4个
最大团为:
[[0, 1, 3, 4], [1, 2, 3, 4]]

E=[[1,3,4],[2,3,4],[4],[4],[]]

step0: [[0], [1], [2], [3], [4]]
step1: [[0, 1], [0, 3], [0, 4], [1, 2], [1, 3], [1, 4], [2, 4], [3, 4]]
step2: [[0, 1, 3], [0, 1, 4], [0, 3, 4], [1, 2, 4], [1, 3, 4]]
step3: [[0, 1, 3, 4]]
step4: []
最大团数目是4个
最大团为:
[[0, 1, 3, 4]]

其他的也没怎么测试了,如果有bug,各位读者看着改改吧= =。提供一个思路,仅作参考。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: