(python)最小生成树实验
实验题目
最小生成树实验
实验要求
画出运行时间与n变化曲线对比图,并分析原因
实验目的
1、掌握什么是最小生成树以及最小生成树是解决什么类型的问题。
2、用Kruskal算法和Prim算法生成最小生成树
3、比较Kruskal算法和Prim算法不同n值生成最小生成树所耗费的时间
实验步骤
1、创建地图
#创建地图
def create_Map(size):
#地图的大小为x*x
Map = np.zeros((size,size))
for i in range(size):
for j in range(i+1,size,1):
#点i到点j的距离相等,假设他们的距离是1-100之间的整数
Map[i][j] = np.random.randint(1,100)
Map[j][i] = Map[i][j]
return Map
2、Kruskal算法生成最小生成树
算法思想:考虑图中权值最小的边,如果加入这条边不会成环,则加入;否则考虑下一条边,直到包含了所有顶点
实现:
1、将图中的所有边都去掉
2、将边按权值从小到大的顺序添加到图中, 保证添加的过程中不会形成
3、重复上一步直到连接所有顶点,此时就生成了最小生成树
#Kruskal算法
def Kruskal(Map,size):
#列表的下标表示不同的顶点,列表中某些位置的元素如果数值相同
#表示这些位置的顶点已经被连接成了一个连通分量
farther = [i for i in range(size)]
tree,edges = [],[]
#将所有的顶点所对应的出边的终点及其权值加入到edges中.
for vi in range(size):
for i in range(size):
if Map[vi][i]!=0:# vi是出边的始点
edges.append((Map[vi][i],vi,i))
#将所有的边按照权值由小到大来排序。
edges.sort()
#按顺序依次取出边,先取最小的
for w, vi, vj, in edges:
#如果reps列表中vi、vj位置的元素值不相等,说明vi、vj是两个不同的连通分量
if farther[vi] != farther[vj]:
#如果不是同一个连通分量,则将这条边的始点和终点、权值存入tree中
tree.append((vi, vj, w))
#如果mst中已经添加了n-1条边了,则终止程序
if len(tree) == size-1:
break
#将两个不同的连通分量vi、vj合并为一个连通分量后,需要更新连通分量vj对应在
#farther表中的值,将其更新为vi对应在farther表中的值
#遍历farther中所有位置
#如果有元素与vj对应在farther元素中值相等,则都更新为vi所对应的farther中的值
#因为vj连通分量里面可能对应着多个顶点,所以都需要将其进行更新
farther_i, farther_j = farther[vi], farther[vj]
for k in range(size):
if farther[k] == farther_j:
farther[k] = farther_i
return tree
3、Prim算法生成最小生成树
算法思想:从某一顶点出发,假设为A,此时A属于最小生成树结点中的一个元素,假设该集合为U,剩下的B-F为待判定的点,此时选取U中的顶点到其余顶点的一个路径最小的边,并且将其中非U中的顶点加入到U中,循环直到U中的顶点包含图所有的顶点为止。
#Prim算法
def Prim(Map,size):
tree = []
#标记第i个节点是否已经被访问过
book = [None]*size
#定义优先队列,优先选择权值小的
pq = PriorityQueue(maxsize=0)
#用第0个节点初始化优先队列
pq.put((0,0,0))
#统计节点个数当count==size时表示已访问过所有节点
count = 0
while count < size and not pq.empty():
#获取第u个节点到其他节点的权值(从权值最小获取到最大)
w,u,v = pq.get()
if book[v]:
continue
#将节点u->v加入到最小生成树中
if count!=0:
tree.append((u,v,w))
#标记节点V已经被访问过,访问节点数加1
book[v] = 1
count += 1
#将所有的顶点所对应的出边的终点及其权值加入到edges中.
for i in range(size):
if not book[i] and Map[v][i]!=0:# v是出边的始点
pq.put((Map[v][i], v, i))
return tree
4、统计函数运行时间
#函数运算时间
def time_Counter(func, x, y):
time_start = time.time()
func(x,y)
time_end = time.time()
return time_end-time_start
5、可视化显示实验结果
def ShowTimes(N,time1, time2):
#设置汉字格式
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
plt.figure("Lab7")
plt.title(u'Kruskal算法和Prim算法生成最小生成树',FontProperties=font)
plt.xlabel(u'城市个数',FontProperties=font)
plt.ylabel(u'所需时间(s)',FontProperties=font)
plt.plot(N,time1,color='black')
plt.plot(N,time2,color='red')
Kruskal = mlines.Line2D([], [], color='black', marker='.',
markersize=10, label='Kruskal')
Prim = mlines.Line2D([], [], color='red', marker='.',
markersize=10, label='Prim')
plt.legend(handles=[Kruskal,Prim])
plt.show()
6、测试代码
#测试算法是否正确
def test(n):
Map = create_Map(n)
print('随机生成的地图为:\n',Map)
tree1 = Kruskal(Map,n)
print('Kruskal算法得到的最小生成树为:\n',tree1)
tree2 = Prim(Map,n)
print('Prim算法得到的最小生成树为:\n',tree2)
7、主函数设计
if __name__ == '__main__':
N = []#保存横坐标
time1 = []#保存Kruskal算法不同n值生成最小生成树所耗费的时间
time2 = []#保存Prim算法不同n值最小生成树所耗费的时间
for i in range(4,1000,100):
Map = create_Map(i)
time1.append(time_Counter(Kruskal,Map,i))
time2.append(time_Counter(Prim,Map,i))
N.append(i)
test(5)
ShowTimes(N,time1,time2)
8、输出显示结果
总结
Kruskal算法难点:
1、如何从所有边中选择代价最小的边?
将所有边加入到一个列表中,按照边的权值进行排序。权值小的优先级高。
2、如何判断加入一条边后不会形成回路?
用并查集来实现。将一个连通分量表示为并查集中的一个子集,通过比较两个端点是否属于一个子集,若是,则表示两个端点已经连通,加入这条边会形成回路,否则将这条边加入生成树,将两个端点所属的子集归并起来,表示其中所有的顶点都已连通。
Prim算法难点:
1、如何从所有边中选择代价最小的边?
将所有边加入到一个列表中,按照边的权值进行排序。权值小的优先级高。
2、如何判断加入一条边后不会形成回路?
用并查集来实现。将一个连通分量表示为并查集中的一个子集,通过比较两个端点是否属于一个子集,若是,则表示两个端点已经连通,加入这条边会形成回路,否则将这条边加入生成树,将两个端点所属的子集归并起来,表示其中所有的顶点都已连通。
全部实验代码
import time
import random
import numpy as np
from queue import PriorityQueue
from matplotlib.font_manager import FontProperties
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
#创建地图
def create_Map(size):
#地图的大小为x*x
Map = np.zeros((size,size))
for i in range(size):
for j in range(i+1,size,1):
#点i到点j的距离相等,假设他们的距离是1-100之间的整数
Map[i][j] = np.random.randint(1,100)
Map[j][i] = Map[i][j]
return Map
#Kruskal算法
def Kruskal(Map,size):
#列表的下标表示不同的顶点,列表中某些位置的元素如果数值相同
#表示这些位置的顶点已经被连接成了一个连通分量
farther = [i for i in range(size)]
tree,edges = [],[]
#将所有的顶点所对应的出边的终点及其权值加入到edges中.
for vi in range(size):
for i in range(size):
if Map[vi][i]!=0:# vi是出边的始点
edges.append((Map[vi][i],vi,i))
#将所有的边按照权值由小到大来排序。
edges.sort()
#按顺序依次取出边,先取最小的
for w, vi, vj, in edges:
#如果reps列表中vi、vj位置的元素值不相等,说明vi、vj是两个不同的连通分量
if farther[vi] != farther[vj]:
#如果不是同一个连通分量,则将这条边的始点和终点、权值存入tree中
tree.append((vi, vj, w))
#如果mst中已经添加了n-1条边了,则终止程序
if len(tree) == size-1:
break
#将两个不同的连通分量vi、vj合并为一个连通分量后,需要更新连通分量vj对应在
#farther表中的值,将其更新为vi对应在farther表中的值
#遍历farther中所有位置
#如果有元素与vj对应在farther元素中值相等,则都更新为vi所对应的farther中的值
#因为vj连通分量里面可能对应着多个顶点,所以都需要将其进行更新
farther_i, farther_j = farther[vi], farther[vj]
for k in range(size):
if farther[k] == farther_j:
farther[k] = farther_i
return tree
#Prim算法
def Prim(Map,size):
tree = []
#标记第i个节点是否已经被访问过
book = [None]*size
#定义优先队列,优先选择权值小的
pq = PriorityQueue(maxsize=0)
#用第0个节点初始化优先队列
pq.put((0,0,0))
#统计节点个数当count==size时表示已访问过所有节点
count = 0
while count < size and not pq.empty():
#获取第u个节点到其他节点的权值(从权值最小获取到最大)
w,u,v = pq.get()
if book[v]:
continue
#将节点u->v加入到最小生成树中
if count!=0:
tree.append((u,v,w))
#标记节点V已经被访问过,访问节点数加1
book[v] = 1
count += 1
#将所有的顶点所对应的出边的终点及其权值加入到edges中.
for i in range(size):
if not book[i] and Map[v][i]!=0:# v是出边的始点
pq.put((Map[v][i], v, i))
return tree
#函数运算时间
def time_Counter(func, x, y):
time_start = time.time()
func(x,y)
time_end = time.time()
return time_end-time_start
def ShowTimes(N,time1, time2):
#设置汉字格式
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
plt.figure("Lab7")
plt.title(u'Kruskal算法和Prim算法生成最小生成树',FontProperties=font)
plt.xlabel(u'n值',FontProperties=font)
plt.ylabel(u'所需时间(s)',FontProperties=font)
plt.plot(N,time1,color='black')
plt.plot(N,time2,color='red')
Kruskal = mlines.Line2D([], [], color='black', marker='.',
markersize=10, label='Kruskal')
Prim = mlines.Line2D([], [], color='red', marker='.',
markersize=10, label='Prim')
plt.legend(handles=[Kruskal,Prim])
plt.show()
#测试算法是否正确
def test(n):
Map = create_Map(n)
print('随机生成的地图为:\n',Map)
tree1 = Kruskal(Map,n)
print('Kruskal算法得到的最小生成树为:\n',tree1)
tree2 = Prim(Map,n)
print('Prim算法得到的最小生成树为:\n',tree2)
if __name__ == '__main__':
N = []#保存横坐标
time1 = []#保存Kruskal算法不同n值生成最小生成树所耗费的时间
time2 = []#保存Prim算法不同n值最小生成树所耗费的时间
for i in range(4,1000,100):
Map = create_Map(i)
time1.append(time_Counter(Kruskal,Map,i))
time2.append(time_Counter(Prim,Map,i))
N.append(i)
test(5)
ShowTimes(N,time1,time2)