数据结构之图:加权无向图与寻找最小生成树,Python——27
加权无向图与prim算法和Kruskal算法寻找最小生成树
加权无向图的介绍
引入
- 加权无向图是一种为每条边关联一 个权重值或 是成本的图模型。这种图能够自然地表示许多应用。在一副航空图中,边表示航线,权值则可以表示距离或是费用。在一副电路图中,边表示导线,权值则可能表示导线的长度即成本,或是信号通过这条先所需的时间。此时我们很容易就能想到,最小成本的问题,例如,从西安飞纽约,怎样飞才能使时间成本最低或者是金钱成本最低?
- 给图的边赋予一定的权重标准,构成加权无向图
加权无向图中的边我们就不能简单向之前一样使用v-w两个顶点表示了(之前的adj_list列表中存放的是顶点,而必须要给边关联一个权重值,因此我们可以使用对象来描述一条边(现在要存放边对象Edge)。
构造加权无向边
属性及方法设计
- 构造方法__init__()中
vertex1和vertex2分别表示要构造的边的两端的顶点;weight表示要构造的边的权重 - get_weight()获取边的权重
- either() 获取当前边的其中一个顶点
- opposite(v) 获取指定顶点的相反方向的顶点(以当前边对象Edge为媒介,存在唯一的相反方向的顶点),一般用在获取了一个顶点之后
- 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),是一幅连通的加权无向图中所有的边集合的一个子集,并且这些边必须连接所有顶点,这个子集它必须是无环的、权重和是最小的
- 对于连通图,一个图中可能有多颗生成树,最小生成树其实是最小权重生成树的简称。
- 广义上而言,对于非连通无向图来说,它的每一连通分量同样有最小生成树,它们的并被称为最小生成森林。
约定
- 只考虑连通图。最小生成树的定义说明它只能存在于连通图中,如果图不是连通的,那么分别计算每个连通图子图的最小生成树,合并到一起称为最小生成森林。
因为非连通图,无法找出连接全部顶点的边 - 所有边的权重都各不相同。如果不同的边权重可以相同,那么一 副图的最小生成树就可能不唯一 了 ,虽然我们的算法可以处理这种情况,但为了好理解,我们约定所有边的权重都各不相同。
最小生成树的性质
- 用一条边连接最小生成树中的任意两个(不相邻的)顶点都会产生一 个新的环;
- 删除最小生成树的任意一条边,将会得到两棵独立的树;
寻找最小生成树(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。
主要属性和方法设计
- 构造方法__init__()中
UFT是一个优化后的并查集UF_Tree_Weighted对象,索引代表顶点,使用其in_the_same_group(v, w)可以判断两个顶点是否在同一颗树上,使用其unite()方法可以将两个顶点所在的树合并
all_edges 是一个最小优先队列MinPriorityQueue的对象,储存图中所有的边,并使用最小优先队列进行按照权重对边进行排序
edges_MST 是一个列表(当做队列),储存了MST的所有边,get_all_edges_mst()返回的就是它,可以直观地观察MST的边 - 生成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相关推荐
- 数据结构之图:无向图的介绍与功能实现,Python——22
无向图(Undigraph)的介绍 引入 生活中的图,有地图,集成电路板的图,可以看类似的看做是数据结构中的图 数据有"一对一","一对多"和"多对多 ...
- 数据结构之图:用图解决案例,Python代码实现——24
用图解决畅通工程案例与途径查找 代码中需要引入的类方法代码链接: 无向图Undigraph 深度优先搜索DFS与广度优先搜索BFS 畅通工程-续 介绍 案例和之前并查集中实现的一样,但问题略有改动,需 ...
- 加权无向图与最小生成树(Prim算法和Kruskal算法)
目录 0 引入 1 图的最小生成树定义及相关约定 2 最小生成树原理 2.1 性质 2.2 切分定理 3 贪心思想 4 Prim算法 4.1 算法步骤 4.2 API设计 4.3 Java代码演示 5 ...
- Java数据结构与算法:无向图,有向图,带权图,图的遍历,最小生成树
文章目录 无向图 有向图 带权图 图的遍历 广度优先遍历 深度优先遍历 最小生成树 无向图 前面了解到树是有单一根结点的非线性结构,图(graph)也是一种非线性结构,其中的结点可以与许多其他的结点相 ...
- 考研复习之数据结构笔记(十二)图(下)(图的应用,包含最小生成树、最短路径、拓扑排序、关键路径以及单元小结)
目录 一.图的应用 1.1 最小生成树 (1)基本概念与问题引入 (2)Prim(普里姆)算法 (3)Kruskal(克鲁斯卡尔)算法 1.2 最短路径 (1)基本概念与问题引入 (2)Dijkstr ...
- 大话数据结构笔记-图
大话数据结构笔记-图 定义 图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为 G(V,E), 其中 G表示一个图, V是图G中的顶点的集合, E是图G中边的集合. 顶点就是图中 ...
- 【数据结构】图(最短路径Dijkstra算法)的JAVA代码实现
最短路径的概念 最短路径的问题是比较典型的应用问题.在图中,确定了起始点和终点之后,一般情况下都可以有很多条路径来连接两者.而边或弧的权值最小的那一条路径就称为两点之间的最短路径,路径上的第一个顶点为 ...
- 大一新生必看,自学必看,里昂详解数据结构之图
数据结构之图 有幸掌握浅薄知识,不吝分享,保持独立思考,自主学习,共同进步.另求关注,点赞,评论,感谢!(tips:主页有数据结构全部知识点,以及知识点讲解,建立完善的数据结构知识体) 核心算法思想 ...
- 数据结构:图(一) 图的基本知识
现实世界中,事物之间的关系是错综复杂的,最简单的是线性关系(线性表,至多有一个前驱一个后继),稍微复杂一些的是树关系(每个元素至多有一个前驱,可能有零个或多个后继). 这片博客我讲一种更为复杂的数据结 ...
最新文章
- android信息中字符个数,在android中指定编辑文本中的字符数
- [Qt教程] 第32篇 网络(二)HTTP
- Error while compiling statement: FAILED: LockException [Error 10280]
- 推荐asp.net调试工具:miniASPNETServer
- velodyne显示点云中grid的单位_孝感好的舞台背景显示屏施工创新服务_胜辉屏安...
- 清空StringBuilder的三种方法及效率
- jquery的extend和fn.extend
- 在Java中使用标准输入输出设备进行字符串,整数浮点数等 的输入输出操作
- 小米9开发版自带root吗_怎么获取root权限-小米CC9和小米CC9美图版刷入MIUI开发版,获取完美ROOT权限功能...
- 环信的使用以及注意事项!
- 夜了,为什么还不想睡
- 红帽牵手阿里云,水到渠成的合作
- 入门训练圆的面积_入门
- 什么是PCB中的net和vl孔
- Leetcode 860. 柠檬水找零(贪心) 记录反思
- 文档中如何插入计算机剪贴画,如何在Word中使用菜单命令插入图片和剪贴画
- Linux命令大全【中文手册】
- 【无标题】ubuntu添加文件到mkinitramfs命令生成的initramfs中
- python自动领取淘金币完整代码,这个脚本值一千不!
- 安卓-system.img镜像文件过大问题
热门文章
- Django项目--csrf攻击
- Python 之 Python2 和 Python3 的区别
- python安装环境傻瓜式安装_前后端分离——前端开发环境傻瓜式一步到位 nodejs ruby python nginx 安装搭建配置...
- Source Insight上手教程
- 加速度计和陀螺仪数据融合
- 深入理解卷积层,全连接层的作用意义
- CATia对计算机配置要求,【2人回答】求CATIA对电脑的详细配置要求-3D溜溜网
- 为什么NX10帮助功能无法找到HTML,NX10.0 新功能介绍视频教程专辑
- html加载js文件失败,firefox/chrome动态设置script加载js文件失败
- mysql 最值复杂查询_MySQL高级查询