(python)最小生成树实验

(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)
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值