python应用——最小生成树实现旅行商问题

一.题目要求

    用最小生成树结合所提供的论文解决旅行商问题。

二.思路分析

Step1:用Prim算法生成最小生成树

Step2:根据最小生成树中各边的权重及各点的度,从大到小对最小生成树进行剪枝操作,使各点的度为01。这个功能在程序中使用函数jianzhi实现的。

Step3:将剪枝的结果连接成环,这个功能在程序中使用cicle函数实现。需要分为3步:

第一步:连接度为0的孤立点,使各点的度大于0,连接成一个个子图

第二步:处理当度为1的点数大于2的情况,将各子图连接为一条链

第三步:连接剩下的两个度为1的点,成为一个环

三.实验程序

# -*-coding:gbk-*-
from numpy import *
import Graph #Graph邻接矩阵类,使用到的功能:.out_edges提供连接节点表(连接节点,权重)///.vertex_num获得顶点数
import  PrioQueue #基于堆结构实现优先队列的类
from Graph import  Graph
from PrioQueue import  PrioQueue
##############################最小生成树算法
def Prim(graph):
    vnum = graph.vertex_num()#顶点数
    
mst = [None]*vnum    #最小生成树边表,每个元素的形式为((i,j)w),初始所有点都不属于U,为None。最小生成树原则上有n-1条边,为实现方便,加入了((0,0,),0),共有n条
    
cands = PrioQueue([(0, 0, 0)]) # 记录候选最短边,形式为(w, vi, vj),vi到vj的权重为w。元组的大小比较逐个比较各个元素,这里首先比较w
    
count = 0#记录最小生成树顶点数
    # 第一次迭代将设置 mst[0] = ((0, 0), 0),不必专门写
    
while count < vnum and not cands.is_empty():#顶点数为n或发现网络不连通
        
w, u, v = cands.dequeue() # 顶端弹出,向下筛选,取当时的最短边的(w,i,j)
        
if mst[v]:   #该顶点已加入最小生成树,边表非空
            
continue             # 邻接顶点v已在mst,跳出本次循环继续,弃掉该边
        
mst[v] = ((u, v), w) # 记录新的MST边和顶点
        
count += 1
        for vi, w in graph.out_edges(v): # 考虑v的邻接顶点vi,某行连接节点表[(节点,权重)。。。]
            
if not mst[vi]:        # 如果对已知在U中的v,相邻接的vi不在U中,则将这条边列入侯选边
                
cands.enqueue((w, v, vi))#尾端插入,向上筛选
    
return mst


##############################剪枝
def jianzhi(tree):#最小生成树tree((i,j),S)
    
tree.remove(((0, 0), 0))#删去为计算方便使用的第一个顶点到自身的边
    #排序后从大到小删去度大于2的边
    
treesort=sorted(tree,key=lambda S: S[1],reverse=True)#lambda是匿名函数,入口为S,返回S[1],将边按距离从大到小进行排序
    
print('Prim tree按边距从大到小排序treesort:',treesort)
    finddu=dict()
    for ((u, v), w) in treesort:#建立最小生成树各点度字典
        
if u not in finddu:
            finddu[u]=1
        else:finddu[u]+=1
        if v not in finddu:
            finddu[v]=1
        else:finddu[v]+=1
    print('最小生成树的度finddu:',finddu)
    remove2index=[]
    for ((u, v), w) in treesort:
        if finddu[u]>2 or finddu[v]>2:
            finddu[u]-=1
            finddu[v] -= 1
            remove2index.append(((u, v), w))
    # print(remove2index)
    
for i in remove2index:#若提前删去会影响上一个for
        
treesort.remove(i)  # 删除列表指定元素,如果某边关联顶点的度大于2,则删掉该边
    
return treesort
#################################将剪枝后的非连通图处理为环
def cicle(myjianzhi,mymat,mat2):#剪枝后的非连通图,图对象
    
def matcomdu(tu):#计算度矩阵
        
zeromat =[0] * mymat.vertex_num()# 1*n行度矩阵
        #zeromat = zeros([mymat.vertex_num(), 1])
        
for ((u, v), w) in tu:  # 建立剪枝后各点度字典
            
zeromat[u] += 1
            zeromat[v] += 1
        return (zeromat)

    def onenum(dumat):#计算1个个数
        
num = 0
        for i in dumat:
            if i == 1:
                num += 1
        return num

    def searchminconnect(a,b,mat2):#将a,b集合中的点各选两个进行最小连接
        
minW=inf
        for i in a:
            for j in b:
                if ((mat2[i][j]<minW) & (mat2[i][j]!=0)):
                    x=i
                    y=j
                    minW=mat2[i][j]
        return (x,y)#(x,y)为集合a,b中的最小连接点


    
addtu=[myjianzhi[i][:] for i in range(len(myjianzhi))]#不同于剪枝图的全新连接图
    
dumat=matcomdu(addtu)#树中的度矩阵
    
print('#################处理第一步:连接度为0的孤立点#################')
    while 0 in dumat:
        onepoint=[]
        anotherpoint =[]
        for i in range(len(dumat)):
            if dumat[i]<2 :#0或1
                
anotherpoint.append(i)
        onepoint.append(dumat.index(0))
        anotherpoint.remove(dumat.index(0))
        (a2,b2)=searchminconnect(onepoint,anotherpoint,mat2)
        addtu.append(((a2,b2),mat2[a2][b2]))
        dumat = matcomdu(addtu)
    print('第一步去度为0的点,度矩阵dumat:', dumat)
    print('第一步去度为0的点,处理后的图addtu:',addtu)
    print('#################处理第二步:当度为1的点数大于2的情况#################')

    while ((0 not in dumat) & (onenum(dumat)>2)):
        onepoint = []
        anotherpoint = []
        findzitu=[0] * mymat.vertex_num()#用于生成第一个连通子图
        
addtu1=[addtu[i][:]for i in range(len(addtu))]#备选边,用于删去连通子图时用过的点
        
num1=0
        deleteindex = [inf]#inf是为了初始化任意赋的值
        # for i in range(len(dumat)):
        
while deleteindex!=[]:#用于findzitu生成第一个连通子图的度矩阵
            
deleteindex = []
            for ((a3, b3), w3) in addtu1:
                if num1 == 0:#选第一条边相关的边作为第一个子图
                    
num1 = 1
                    findzitu[a3] += 1
                    findzitu[b3] += 1
                    deleteindex.append(((a3, b3), w3))
                else:#第一条边以外的相邻边
                    
if (findzitu[a3] != 0 or findzitu[b3] != 0):#有邻接点
                        
findzitu[a3] += 1
                        findzitu[b3] += 1
                        deleteindex.append(((a3, b3), w3))
            # print('deleteindex',deleteindex)
            # print('addtu1',addtu1)
            
for i in deleteindex:#删去备选边中本次循环中用过的邻接边
                
addtu1.remove(i)
        print('第二步中找到的一条子图中各点的度',findzitu)
###################################
        
first1point=True#判断是否为第一个度1点
        
for i in range(len(findzitu)):
            if findzitu[i]==1 & first1point==True:
                onepoint.append(i)
                first1point =False
            if
findzitu[i]==1 & first1point!=True:
                anotherpoint.append(i)
###########################################
        
for i in range(len(dumat)):
            if dumat[i]==1 & dumat[i]not in onepoint:#其他度为1的点
                
anotherpoint.append(i)
        (a5, b5) = searchminconnect(onepoint, anotherpoint,mat2)
        addtu.append(((a5, b5), mat2[a5][b5]))
        dumat = matcomdu(addtu)
        # print('2,addtu:', addtu)
    
print('第二步减少度为1的点,度矩阵dumat:', dumat)
    print('第二步减少度为1的点,处理后的图addtu:', addtu)
    print('#################处理第三步:连接剩下的两个度为1的点#################')

    if( (0 not in dumat) & (onenum(dumat) ==2)):
        x=inf
        for i in range(len(dumat)):
            if (dumat[i]==1) & (x==inf):
                x=i
            if (dumat[i]==1) &(x!=inf) :
                y=i
        addtu.append(((x, y), mat2[x][y]))
        print('处理第三步将两头连成环后的addtu:', addtu)
    return  addtu

四.主程序


#######################
def main():
    mat2=[[0,3,3,5,16,5,12,21,23],#
          
[3,0,2,2,9,4,7,18,19],
          [3,2,0,7,13,9,15,22,24],
          [5,2,7,0,7,2,2,12,14],
          [16,9,13,7,0,15,9,20,11],#
          
[5,4,9,2,15,0,4,10,17],
          [12,7,15,2,9,4,0,6,8],
          [21,18,22,12,20,10,6,0,10],
          [23,19,24,14,11,17,8,10,0]]#
    
mymat = Graph(mat2)#构造图对象
    
mymst=Prim(mymat)#构造最小生成树
    
print('the Prim tree is',mymst)
    myjianzhi=jianzhi(mymst)#去除度大于2的部分
    
print('删去度大于2的边后的树:', myjianzhi)
    mycicle=cicle(myjianzhi,mymat,mat2)#剪枝后的非连通图,图对象
    
print('#################旅行商问题的最终解#################')
    print('处理后的环',mycicle)





if __name__=='__main__':
    main()

五.实验结果

C:\Users\Administrator\AppData\Local\Programs\Python\Python36\python.exe C:/Users/Administrator/PycharmProjects/untitled1/lvxingshang.py

the Prim tree is [((0, 0), 0), ((0, 1), 3), ((1, 2), 2), ((1, 3), 2), ((3, 4), 7), ((3, 5), 2), ((3, 6), 2), ((6, 7), 6), ((6, 8), 8)]

Prim tree按边距从大到小排序treesort[((6, 8), 8), ((3, 4), 7), ((6, 7), 6), ((0, 1), 3), ((1, 2), 2), ((1, 3), 2), ((3, 5), 2), ((3, 6), 2)]

最小生成树的度finddu{6: 3, 8: 1, 3: 4, 4: 1, 7: 1, 0: 1, 1: 3, 2: 1, 5: 1}

删去度大于2的边后的树: [((6, 7), 6), ((1, 2), 2), ((3, 5), 2), ((3, 6), 2)]

#################处理第一步:连接度为0的孤立点#################

第一步去度为0的点,度矩阵dumat: [1, 2, 1, 2, 1, 1, 2, 1, 1]

第一步去度为0的点,处理后的图addtu: [((6, 7), 6), ((1, 2), 2), ((3, 5), 2), ((3, 6), 2), ((0, 1), 3), ((4, 8), 11)]

#################处理第二步:当度为1的点数大于2的情况#################

第二步中找到的一条子图中各点的度 [0, 0, 0, 2, 0, 1, 2, 1, 0]

第二步中找到的一条子图中各点的度 [2, 2, 1, 2, 0, 2, 2, 1, 0]

第二步减少度为1的点,度矩阵dumat: [2, 2, 2, 2, 2, 2, 2, 1, 1]

第二步减少度为1的点,处理后的图addtu: [((6, 7), 6), ((1, 2), 2), ((3, 5), 2), ((3, 6), 2), ((0, 1), 3), ((4, 8), 11), ((5, 0), 5), ((2, 4), 13)]

#################处理第三步:连接剩下的两个度为1的点#################

处理第三步将两头连成环后的addtu: [((6, 7), 6), ((1, 2), 2), ((3, 5), 2), ((3, 6), 2), ((0, 1), 3), ((4, 8), 11), ((5, 0), 5), ((2, 4), 13), ((7, 8), 10)]

#################旅行商问题的最终解#################

处理后的环 [((6, 7), 6), ((1, 2), 2), ((3, 5), 2), ((3, 6), 2), ((0, 1), 3), ((4, 8), 11), ((5, 0), 5), ((2, 4), 13), ((7, 8), 10)]

Process finished with exit code 0

六.结果分析

 

首先对比一下调试结果与论文中的结果。将调试结果转化为连通图如下图所示:

 

注:由于python中索引是从0开始的,所以程序将各点编号为0,1,2...8对应于论文中的1,2,3...9

 

论文中的连通图为:

 

两者之间的连接方式显然不同,但是论文中各边权重之和为58,而调试结果中,各边权重之和为54。由此可见,程序在本例中运行结果是优于参考论文的。有多种可能导致了这种情况的发生:

1. 在对最小生成树剪枝时,需要对prim树中的边权重进行排序,而prim树中有多条权重为2的边,对这些权重为2的边的排序方式的不同会导致剪枝后的结果不同。

2. Step3:将剪枝的结果连接成环”这一步中,考虑到时间复杂性,并不是从整体最优上加以考虑,而是选择算法遇到的第一个度为0/1的点作为第一个需要连接的点,然后从余下的点中运用贪心算法,找到某种意义上的局部最优解。对“第一个需要连接的点”的选择也会导致最后结果的不同。

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/weixin_41819299/article/details/80840191