网络算法——基于堆的Prim算法和基于并查集的Kruskal算法

本文实现了基于堆的Prim算法和基于并查集的Kruskal算法,用于找到图的最小生成树。通过networkx库进行图的可视化,并使用matplotlib展示结果。测试表明,在节点数较少时,Kruskal算法效率更高,但在节点数增多和稠密图情况下,Prim算法表现更优。文章还讨论了存储结构的选择和优化空间,以及两种算法在稀疏图和稠密图上的性能差异。
摘要由CSDN通过智能技术生成

1 实现概述

实验实现了Prim(基于堆)和Kruskal(基于UNION-FIND)算法。堆和并查集结构由自己定义。图的定义使用库networkx,能方便的为节点和边赋予信息。并且结合networkx和matplotlib将图可视化。在测试上使用随机数通过节点,边和权重的随机生成进行验证,计算算法运行时间进行比对。

在堆的结构上,内部使用三个数组分别记录节点,对应节点的最小权重和邻接点。每次在上下调整的时候同时调整三者。在更新值的时候通过节点的列表找到数组中的位置,然后视情况上下调整而非重新建堆。

在并查集的结构上,采用字典的数据结构key为head,value为属于head的所有节点。查询时候只需在value中查找,合并将value合并并删除对应元素较少的key即可。

2 效果演示

输入图:

img

执行结果:

img

image-20230624155332644

3 堆和并查集类定义

类名:Heap

类属性:

  • self.heap = list() 记录堆中的值 对应边的权重

  • self.node = list() 记录堆中权重对应的起始节点

  • self.neighbor = list() 记录堆中节点的邻接节点

    类方法:

类方法 作用
def _init_(self): 初始化类
def _str_(self): 返回类的信息
def Up(self, i: int): 将指定节点向上调整
def Down(self, i: int): 将指定节点向下调整
def Insert(self, weight: int, node: str, neighbor: str): 拆入新节点
def GetMin(self): 获取堆的最小值
def Change(self, weight: int, node: str, neighbor: str): 更改指定节点的值

类名:Union_Find

类属性:

  • self.data = dict() 采用字典的方式,key为head value为属于head的所有点

    类方法:

类方法 作用
def _init_(self): 初始化类
def _str_(self): 返回类的信息
def Find(self, node: str): 放回节点对应的head
def Union(self, head1: str, head2: str): 将两个head合并

4 其他函数定义

函数 作用
def Prim(g: nx.Graph, begin: str) -> nx.Graph: 使用prim算法将给定图转化成最小生成树
def Kruskal(g: nx.Graph) -> nx.Graph: 使用kruskal算法将给定图转化成最小生成树
def show(g: nx.Graph, name: str): 调用networkx和matplotlib库打印输入图
def Test(test_times: int, max_nodes: int, max_weights): 运行时间测试,参数分别制定测试次数,最大节点数目和最大权重值。

5 测试集

在这部分中,通过指定测试集数量,最大节点数和最大权重数目,我们使用random模块随机生成节点,边和权重,通过networkx库的内置函数is_connected检验随机生存的图是否联通,若是则放入生产树算法,最后分别统计运行时间,我们以30000次测试,50为最大权重。分别测试10、20、50、100、200节点数目下的运行时间。如下图所示输出:

img

统计如下表所示:

时间/节点数 10 20 40 100 200
Prim时间 1.317 4.922 22.485 78.927 332.968
Kru时间 1.114 4.012 24.599 124.029 668.504
Prim/Kru 1.182226212 1.226819541 0.914061547 0.636359239 0.498079293

可见在节点数较少的时候,kru算法有较好的运行效果,但是当节点增加到100以上的时候,kru算法的性能急剧下降,在200节点的时候性能只有prim算法的1/2左右。

接着我们探讨在节点数一定,运行效率相似的情况下,稀疏图或者是稠密图对于算法的影响,故而,我们选取30000次和30~40节点作为条件,定义稀疏图的边数在 [ n − 1 , n 2 / 4 ] [n-1,n^2/4] [n1,n2/4]之间,稠密图的边数在 [ n 2 / 4 , n 2 / 2 ] [n^2/4,n^2/2] [n2/4,n2/2]之间,比较结果。

时间/类型 40节点稀疏图 40节点稠密图 上升比例
Prim时间 37.597 69.802 0.856584302
Kru时间 37.57 87.027 1.316396061

可见对于稠密图而言,kru算法运行时间相对与prim算法提高幅度更大。

综上所属,kru算法适合较少节点数和稀疏图,在稠密以及较多节点数的时候表现急剧下降,但是kru算法较为简洁,并且有更低的空间复杂度。

6 遇到的疑惑

首先是选择怎样的存储结构的问题,之前的proj1逻辑上使用了邻接表,实际上借助字典的数据结构实现。但如果为图增加权重信息,又会重新设计,并且也不便于以后项目的进行。

所以选择了现成的网络图分析的库networkx,省去了考虑图的构建,能更容易的关注于算法本身。

两种算法的思路都容易掌握,问题是如何构建堆和并查集,其中重点又是堆的构建。我们没有使用库heapq,因为考虑到更新的时候需要删除整个堆重新构建。问题是如何在记录权重的同时记录所属节点和对应临界点。

我们考虑了两种方法,[(node,weight,next),…]的结构和三个列表的结构。我们虽然使用了第二种,但是现在看来应当是第一种的逻辑更为合理并且操作也更加方便。

仍然是堆的建立,应当将位置0空出,从1开始记录数据更为方便。可见在堆上仍有很大的改进空间。

7 源代码

import networkx as nx
import matplotlib.pyplot as plt
import matplotlib as mpl
import time
import random

"""
缺少包请cmd
pip install networkx
pip install matplotlib
如果遇到图片打开后就停止运行的 很抱歉是编辑器的问题 pycharm专业版可以 社区版不行
很抱歉实在调不过来了 把窗口关掉就行了
友情提示 虽然算法跑的快 但是networkx库跑的慢 增加边需要很长时间
测试量和节点不要太大 具体结果看报告就好
"""


class Heap():
    # 函数中的索引按照123开始
    def __init__(self):
        self.heap = list()
        self.node = list()
        self.neighbor = list()

    def __str__(self):
        ans = '堆中元素:' + self.heap.__str__() + '\n' + '对应节点:' + self.node.__str__() + '\n' + '最小邻居节点:' + self.neighbor.__str__()
        return ans

    def Up(self, i: int):
        while True:
            if i < 2:
                return
            if self.heap[i // 2 - 1] > self.heap[i - 1]:
                self.heap[i // 2 - 1], self.heap[i - 1] = self.heap[i - 1], self.heap[i // 2 - 1]
                self.node[i // 2 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值