加权无向图与prim算法和Kruskal算法寻找最小生成树

加权无向图的介绍

引入

  • 加权无向图是一种为每条边关联一 个权重值或 是成本的图模型。这种图能够自然地表示许多应用。在一副航空图中,边表示航线,权值则可以表示距离或是费用。在一副电路图中,边表示导线,权值则可能表示导线的长度即成本,或是信号通过这条先所需的时间。此时我们很容易就能想到,最小成本的问题,例如,从西安飞纽约,怎样飞才能使时间成本最低或者是金钱成本最低?
  • 给图的边赋予一定的权重标准,构成加权无向图

加权无向图中的边我们就不能简单向之前一样使用v-w两个顶点表示了(之前的adj_list列表中存放的是顶点,而必须要给边关联一个权重值,因此我们可以使用对象来描述一条边(现在要存放边对象Edge)。

构造加权无向边

属性及方法设计
  1. 构造方法__init__()中
    vertex1和vertex2分别表示要构造的边的两端的顶点;weight表示要构造的边的权重
  2. get_weight()获取边的权重
  3. either() 获取当前边的其中一个顶点
  4. opposite(v) 获取指定顶点的相反方向的顶点(以当前边对象Edge为媒介,存在唯一的相反方向的顶点),一般用在获取了一个顶点之后
  5. compare(edge) 比较传入的Edge对象的权重和自身的权重,自身权重大返回1,两者相等返回0,否则返回-1
加权无向边Python代码实现
class Edge:def __init__(self, vertex1, vertex2, weight):self.vertex1 = vertex1self.vertex2 = vertex2self.weight = weightdef get_weight(self):return self.weightdef either(self):"""Return either vertex of this Edge"""return self.vertex1def opposite(self, v):return self.vertex1 if v == self.vertex2 else self.vertex2def compare(self, edge):return 1 if self.weight > edge.weight else (-1 if self.weight < edge.weight else 0)

代码较简单,测试就略过了,这里实现的Edge对象将用于后面的类方法实现。

Prim算法跳转
Kruskal算法跳转

Python代码实现加权无向图

from Structure.graph.Edge import Edgeclass WeightedUndigraph:def __init__(self, v):self.num_vertices = vself.num_edges = 0self.adj_list = [[] for _ in range(v)]def get_num_vertices(self):return self.num_verticesdef get_num_edges(self):return self.num_edgesdef add_edge(self, edge: Edge):v1 = edge.either()v2 = edge.opposite(v1)self.adj_list[v1].append(edge)self.adj_list[v2].append(edge)self.num_edges += 1def adjacent_edges(self, vertex):return self.adj_list[vertex]def all_edges(self):all_edges = []for i in range(self.num_vertices):for edge in self.adj_list[i]:if edge.opposite(i) < i:      all_edges.append(edge)return all_edges

最小生成树的介绍

引入
举例

  • 省领导审查全省各市,如何找出最优最快路径完成审查
  • 所有连接这些城市的道路中,路径(权重)最短的则是最优路径,此时要用到最小生成树

最小生成树的定义

  • 最小生成树(Minimum spanning tree)或说最小权重生成树(Minimum weight spanning tree),是一幅连通的加权无向图中所有的边集合的一个子集,并且这些边必须连接所有顶点,这个子集它必须是无环的、权重和是最小的
  • 对于连通图,一个图中可能有多颗生成树,最小生成树其实是最小权重生成树的简称。
  • 广义上而言,对于非连通无向图来说,它的每一连通分量同样有最小生成树,它们的并被称为最小生成森林。

约定

  1. 只考虑连通图。最小生成树的定义说明它只能存在于连通图中,如果图不是连通的,那么分别计算每个连通图子图的最小生成树,合并到一起称为最小生成森林。
    因为非连通图,无法找出连接全部顶点的边
  2. 所有边的权重都各不相同。如果不同的边权重可以相同,那么一 副图的最小生成树就可能不唯一 了 ,虽然我们的算法可以处理这种情况,但为了好理解,我们约定所有边的权重都各不相同。

最小生成树的性质

  • 用一条边连接最小生成树中的任意两个(不相邻的)顶点都会产生一 个新的环;
  • 删除最小生成树的任意一条边,将会得到两棵独立的树;

寻找最小生成树(Minimum Spanning Tree)

在实现寻找最小生成树之前,我们先来了解一下实现寻找最小生成树的两个原理

切分定理

切分定理
先了解概念
要从一副连通图中找出该图的最小生成树,需要通过切分定理完成。

  • 切分:将图的所有顶点按照某些规则分为两个非空且没有交集的集合。
  • 横切边:连接两个属于不同集合的顶点的边称之为横切边。
    例如我们将图中的顶点切分为两个集合,色顶点于一个集合,色顶点属于外一个集合,那么效果如下:

再了解定义
切分定理:

  • 在一副加权图中, 给定任意的切分,它的横切边中的权重最小者必然属于图中的最小生成树。
  • 注意:一次切分产生的多个横切边中,权重最小的边不一定是所有横切边中唯一属于图的最小生成树的边。也有可能非最小权重的横切边也是最小生成树的边

贪心算法(Greedy Algorithm)

定义

  • 任何在想要寻找全局最优方法时,在所有单个阶段进行探索时都尝试寻找当前阶段的局部最优解,以尝试获取最终的全局最优解,这样的算法都可以称之为贪心算法。
  • 在许多问题中,贪心算法的思想并不能总是获得一个最优解,但是贪心算法的探索过程,会生成许多的局部最优解,这些局部最优解在合理的时间内,也可以近似地等于一个全局最优解

Prim算法

计算图的最小生成树的算法有很多种,但这些算法都可以看做是贪心算法的一种特殊情况,这些算法的不同之处在于保存切分和判定权重最小的横切边的方式。

prim算法定义

  • prim算法是一种用于寻找加权无向图中最小生成树的贪心算法,prim算法选择随机的一个顶点开始,把这个顶点当做处于MST的集合,而把其他不在这颗MST中的顶点标记为不处于MST中的集合,然后通过不断的重复做某些操作,可以逐渐将非最小生成树中的顶点加入到最小生成树中,直到所有的顶点都加入到最小生成树中。

Prim算法的切分规则:

  • 把最小生成树中的顶点看做是一个集合 ,把不在最小生成树中的顶点看做是另外一个集合。
  • 最开始是把任意一个结点当做最小生成树的结点;而剩下的其他结点则不是树中的结点

在实现prim算法寻找MST之前,我们要借助一个最小索引优先队列,来方便地取出最小权重边:

  • 引入索引最小优先队列the_cut_edges,它的索引代表图的顶点,储存的值代表从其他的某个顶点到当前索引对应顶点的边的权重,当遍历完某个顶点的邻接边时,the_cut_edges就会储存所有该顶点通向其他顶点的边的权重(重复的权重后面会有设计方法来避免),当最后遍历完时,我们只需要调用索引最小优先队列的delete_min_and_get_index()即可获取最小权重对应的顶点

索引最小有限队列代码传送门
加权无向边,up↑
加权无向图,up↑

Prim算法Python代码实现寻找MST

from Structure.graph.WeightedUndigraph import WeightedUndigraph
from Structure.graph.Edge import Edge
from Structure.PriorityQueue.IndexMinpriorutyQueue import IndexMinPriorityQueue
from math import infclass PrimMST:def __init__(self, graph):"""MST here represent the Minimum Spanning Tree of the current loop"""self.graph = graph# Memorize the cheapest edge to MST of each vertex(index)self.min_edge_to_MST = [None for _ in range(self.graph.get_num_vertices())]# Store the smallest weight of each vertex(index)'s edge to MST;# Initialize it with infinite plus, we will compare out a minimum weight afterself.min_weight_to_MST = [+inf for _ in range(self.graph.get_num_vertices())]# Mark a True if a vertex(index) has been visitedself.marked = [False for _ in range(self.graph.get_num_vertices())]# Memorize the smaller weight of each vertex(index)'s edge connected to MSTself.the_cut_edges = IndexMinPriorityQueue(self.graph.get_num_vertices())# Initialize a 0.0 as the minimum weight to weight_to_MSTself.min_weight_to_MST[0] = 0.0self.the_cut_edges.insert(0, 0.0)while not self.the_cut_edges.is_empty():# Take out the minimum-weighted vertex, and make a visit(update) for itself.visit(self.the_cut_edges.delete_min_and_get_index())def visit(self, v):"""Update the MST"""self.marked[v] = Truefor e in self.graph.adjacent_edges(v):w = e.opposite(v)# Check if the opposite vertex of v in edge e is marked, if did, skip this loopif self.marked[w]:continue# Find out the minimum-weighted-edge vertex opposite to this vertex(v)if e.get_weight() < self.min_weight_to_MST[w]:    # e.get_weight():Get weight of the edge between v and w# Update the minimum edge and weight# print(f"v: {v}, w: {w}, min_weight_edge: {e.get_weight()}")self.min_edge_to_MST[w] = eself.min_weight_to_MST[w] = e.get_weight()if self.the_cut_edges.is_index_exist(w):# print(w, e.get_weight())self.the_cut_edges.change_item(w, e.get_weight())else:self.the_cut_edges.insert(w, e.get_weight())def min_weight_edges(self):return [edge for edge in self.min_edge_to_MST if edge]if __name__ == '__main__':with open('../MST.txt', 'r') as f:num_vertices = int(f.readline())num_edges = int(f.readline())graph = WeightedUndigraph(num_vertices)for e in range(num_edges):v1, v2, w = f.readline().split()graph.add_edge(Edge(int(v1), int(v2), float(w)))P_MST = PrimMST(graph)for e in P_MST.min_weight_edges():v = e.either()w = e.opposite(v)weight = e.weightprint(f"v: {v} w: {w} weight: {weight}")

运行结果

v: 1 w: 7 weight: 0.19
v: 0 w: 2 weight: 0.26
v: 2 w: 3 weight: 0.17
v: 4 w: 5 weight: 0.35
v: 5 w: 7 weight: 0.28
v: 6 w: 2 weight: 0.4
v: 0 w: 7 weight: 0.16

即是最小生成树的所有边的权重和

MST.txt

8
16
4 5 0.35
4 7 0.37
5 7 0.28
0 7 0.16
1 5 0.32
0 4 0.38
2 3 0.17
1 7 0.19
0 2 0.26
1 2 0.36
1 3 0.29
2 7 0.34
6 2 0.40
3 6 0.52
6 0 0.58
6 4 0.93

Kruskal算法

定义

  • kruskal算法图论中寻找加权无向图的最小生成树的另一种贪心算法,它的主要思想是按照边的权重(从小到大)处理它们,将边加入最小生成树中,加入的边不会与已经加入最小生成树的边构成环,直到树中含有N-1条边为止。

kruskal算法和prim算法的区别:

  • Prim算法是一次一条边的构造最小生成树,每一步都为一棵树添加一条边。kruskal算法构造最小生成树的时候也是一次一条边地构造,但它的切分规则是不一样的。它每一次寻找的边会连接一片森林中的两棵树。如果一副加权无向图由V个顶点组成 ,初始化情况下每个顶点都构成一棵独立的树,则V个顶点对应V棵树,组成一片森林, kruskal算法每一次处理都会将两棵树合并为一棵树,直到整个森林中只剩一棵树为止。

实现步骤:

Kruskal算法在寻找MST时,需要用到并查集来实现,当寻找的树不是连通的时候(初始时所有结点都是分隔开的),它就会为在每一颗树中搜寻出一个MST。

主要属性和方法设计

  1. 构造方法__init__()中
    UFT是一个优化后的并查集UF_Tree_Weighted对象,索引代表顶点,使用其in_the_same_group(v, w)可以判断两个顶点是否在同一颗树上,使用其unite()方法可以将两个顶点所在的树合并
    all_edges 是一个最小优先队列MinPriorityQueue的对象,储存图中所有的边,并使用最小优先队列进行按照权重对边进行排序
    edges_MST 是一个列表(当做队列),储存了MST的所有边,get_all_edges_mst()返回的就是它,可以直观地观察MST的边
  2. 生成MST的方法直接在构造方法中实现,调用即可完成

并查集传送、
最小优先队列传送(最小优先队列需要小小地修改比较的方法,应该比较传入的Edge对象的weight,否则直接比较会报错):

    def less(self, i, j):return operator.lt(self.heap[i].weight, self.heap[j].weight)

优化后的并查集传送
最小优先队列传送
加权无向边
加权无向图

Kruskal算法Python代码寻找MST

from Structure.UF.UF_Tree_Weighted import UF_Tree_Weighted
from Structure.PriorityQueue.MinPriorityQueue import MinPriorityQueue
from Structure.graph.WeightedUndigraph import WeightedUndigraph
from Structure.graph.Edge import Edgeclass KruskalMST:def __init__(self, graph):self.graph = graphself.N = self.graph.num_verticesself.UFT = UF_Tree_Weighted(self.N)self.all_edges = MinPriorityQueue()self.edges_MST = []for e in self.graph.get_all_edges():self.all_edges.append(e)while not self.all_edges.is_empty() and len(self.edges_MST) < self.N - 1:min_edge = self.all_edges.extract_min()v = min_edge.either()w = min_edge.opposite(v)if self.UFT.in_the_same_group(v, w):continueself.UFT.unite(v, w)self.edges_MST.append(min_edge)def get_all_edges_mst(self):return self.edges_MSTif __name__ == '__main__':with open('../MST.txt', 'r') as f:num_vertices = int(f.readline())num_edges = int(f.readline())graph = WeightedUndigraph(num_vertices)for e in range(num_edges):v1, v2, w = f.readline().split()graph.add_edge(Edge(int(v1), int(v2), float(w)))K_MST = KruskalMST(graph)for e in K_MST.get_all_edges_mst():v = e.either()w = e.opposite(v)weight = e.weightprint(f"v: {v} w: {w} weight: {weight}")

运行结果:

v: 0 w: 7 weight: 0.16
v: 2 w: 3 weight: 0.17
v: 1 w: 7 weight: 0.19
v: 0 w: 2 weight: 0.26
v: 5 w: 7 weight: 0.28
v: 4 w: 5 weight: 0.35
v: 6 w: 2 weight: 0.4

MST.txt

8
16
4 5 0.35
4 7 0.37
5 7 0.28
0 7 0.16
1 5 0.32
0 4 0.38
2 3 0.17
1 7 0.19
0 2 0.26
1 2 0.36
1 3 0.29
2 7 0.34
6 2 0.40
3 6 0.52
6 0 0.58
6 4 0.93

数据结构之图:加权无向图与寻找最小生成树,Python——27相关推荐

  1. 数据结构之图:无向图的介绍与功能实现,Python——22

    无向图(Undigraph)的介绍 引入 生活中的图,有地图,集成电路板的图,可以看类似的看做是数据结构中的图 数据有"一对一","一对多"和"多对多 ...

  2. 数据结构之图:用图解决案例,Python代码实现——24

    用图解决畅通工程案例与途径查找 代码中需要引入的类方法代码链接: 无向图Undigraph 深度优先搜索DFS与广度优先搜索BFS 畅通工程-续 介绍 案例和之前并查集中实现的一样,但问题略有改动,需 ...

  3. 加权无向图与最小生成树(Prim算法和Kruskal算法)

    目录 0 引入 1 图的最小生成树定义及相关约定 2 最小生成树原理 2.1 性质 2.2 切分定理 3 贪心思想 4 Prim算法 4.1 算法步骤 4.2 API设计 4.3 Java代码演示 5 ...

  4. Java数据结构与算法:无向图,有向图,带权图,图的遍历,最小生成树

    文章目录 无向图 有向图 带权图 图的遍历 广度优先遍历 深度优先遍历 最小生成树 无向图 前面了解到树是有单一根结点的非线性结构,图(graph)也是一种非线性结构,其中的结点可以与许多其他的结点相 ...

  5. 考研复习之数据结构笔记(十二)图(下)(图的应用,包含最小生成树、最短路径、拓扑排序、关键路径以及单元小结)

    目录 一.图的应用 1.1 最小生成树 (1)基本概念与问题引入 (2)Prim(普里姆)算法 (3)Kruskal(克鲁斯卡尔)算法 1.2 最短路径 (1)基本概念与问题引入 (2)Dijkstr ...

  6. 大话数据结构笔记-图

    大话数据结构笔记-图 定义 图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为 G(V,E), 其中 G表示一个图, V是图G中的顶点的集合, E是图G中边的集合. 顶点就是图中 ...

  7. 【数据结构】图(最短路径Dijkstra算法)的JAVA代码实现

    最短路径的概念 最短路径的问题是比较典型的应用问题.在图中,确定了起始点和终点之后,一般情况下都可以有很多条路径来连接两者.而边或弧的权值最小的那一条路径就称为两点之间的最短路径,路径上的第一个顶点为 ...

  8. 大一新生必看,自学必看,里昂详解数据结构之图

    数据结构之图 有幸掌握浅薄知识,不吝分享,保持独立思考,自主学习,共同进步.另求关注,点赞,评论,感谢!(tips:主页有数据结构全部知识点,以及知识点讲解,建立完善的数据结构知识体) 核心算法思想 ...

  9. 数据结构:图(一) 图的基本知识

    现实世界中,事物之间的关系是错综复杂的,最简单的是线性关系(线性表,至多有一个前驱一个后继),稍微复杂一些的是树关系(每个元素至多有一个前驱,可能有零个或多个后继). 这片博客我讲一种更为复杂的数据结 ...

最新文章

  1. android信息中字符个数,在android中指定编辑文本中的字符数
  2. [Qt教程] 第32篇 网络(二)HTTP
  3. Error while compiling statement: FAILED: LockException [Error 10280]
  4. 推荐asp.net调试工具:miniASPNETServer
  5. velodyne显示点云中grid的单位_孝感好的舞台背景显示屏施工创新服务_胜辉屏安...
  6. 清空StringBuilder的三种方法及效率
  7. jquery的extend和fn.extend
  8. 在Java中使用标准输入输出设备进行字符串,整数浮点数等 的输入输出操作
  9. 小米9开发版自带root吗_怎么获取root权限-小米CC9和小米CC9美图版刷入MIUI开发版,获取完美ROOT权限功能...
  10. 环信的使用以及注意事项!
  11. 夜了,为什么还不想睡
  12. 红帽牵手阿里云,水到渠成的合作
  13. 入门训练圆的面积_入门
  14. 什么是PCB中的net和vl孔
  15. Leetcode 860. 柠檬水找零(贪心) 记录反思
  16. 文档中如何插入计算机剪贴画,如何在Word中使用菜单命令插入图片和剪贴画
  17. Linux命令大全【中文手册】
  18. 【无标题】ubuntu添加文件到mkinitramfs命令生成的initramfs中
  19. python自动领取淘金币完整代码,这个脚本值一千不!
  20. 安卓-system.img镜像文件过大问题

热门文章

  1. Django项目--csrf攻击
  2. Python 之 Python2 和 Python3 的区别
  3. python安装环境傻瓜式安装_前后端分离——前端开发环境傻瓜式一步到位 nodejs ruby python nginx 安装搭建配置...
  4. Source Insight上手教程
  5. 加速度计和陀螺仪数据融合
  6. 深入理解卷积层,全连接层的作用意义
  7. CATia对计算机配置要求,【2人回答】求CATIA对电脑的详细配置要求-3D溜溜网
  8. 为什么NX10帮助功能无法找到HTML,NX10.0 新功能介绍视频教程专辑
  9. html加载js文件失败,firefox/chrome动态设置script加载js文件失败
  10. mysql 最值复杂查询_MySQL高级查询