目录

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

0 引入

通过加权无向图结合最小生成树相关算法,可以解决最小成本问题,并找到最小成本对应的顶点和边。

1 图的最小生成树定义及相关约定

图的生成树:图的生成树是它的一个含有其所有顶点的无环连通子图。

图的最小生成树:图的权值(所有边的权重之和)最小的生成树。


约定:最小生成树只存在于连通图中,如果图不是连通的,那么分别计算每个连通子图的最小生成树,再合并到一起形成最小生成森林。


同时为了便于理解,约定所有边的权重都不相同(如果有权重相同的边,最小生成树可能就不唯一了)。

2 最小生成树原理

2.1 性质

  1. 用一条边连接树中的任意两个顶点都会产生环;

  2. 从树中删除任意一条边,将会得到两棵独立的树;

2.2 切分定理

要从一个连通图中找出该图的最小生成树,需要通过切分定理来完成。

切分:将图的所有顶点按照某些规则分为两个非空且没有交集的集合;

横切边:连接两个不同集合的顶点的边称为横切边;

例如将下图中的顶点切分为两个集合,灰色顶点属于一个集合,白色顶点属于另一个集合。

切分定理:在一副加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图的最小生成树。

3 贪心思想

  1. 贪心算法是计算图的最小生成树的基础算法;
  2. 先利用切分定理找到最小生成树的一条边,然后不断重复直到找到最小生成树的所有边;
  3. 如果图有V个顶点,那么需要找到V-1条边,就可以表示该图的最小生成树;









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

下面介绍Prim算法和kruskal算法。

4 Prim算法

4.1 算法步骤

  1. 将最小生成树中的顶点作为一个集合,非最小生成树中的顶点作为另外一个集合。1 索引最小优先队列存储所有的横切边;2 edgeTo数组存储当前顶点和最小生成树之间的最短边;3 distTo数组表示当前顶点和最小生成树之间的最短边的权重,初始值为无穷大;4 marked数组标识当前顶点是否在最小生成树中,如果在,值为true,如果不在,值为false;
  2. 选取起点0,令distTo[0] = 0.0,并将该点加入到索引最小优先队列中pq.insert(0, 0.0);
  3. 调用索引最小优先队列的delMin(),将当下最小横切边对应的顶点加入最小生成树的集合中(标记该顶点:marked数组中该顶点值设为true)。同时遍历该顶点的邻接表,更新edgeTo和distTo,并新增或更新横切边(更新索引最小优先队列);
  4. 重复3,直到索引最小优先队列为空,最终edgeTo数组里存储的就是最小生成树的边;

  1. 先默认0是最小生成树中的唯一顶点,其他顶点都不在最小生成树中,此时横切边就是顶点0的邻接表中0-2,0-4,0-6,0-7这四条边,于是索引优先队列的2、4、6、7索引处分别存储这些边的权重值;

  1. 从这四条横切边中找出权重最小的边,然后把对应的顶点加进来:由于0-7这条横切边的权重最小,因此把顶点7加进来;
  2. 于是顶点7的邻接表中的边也成为了横切边,而0-7已经不是横切边了,让它失效:已经调用了最小索引优先队列的delMin()方法;
  3. 2和4各有两条横切边指向最小生成树,各保留一条:4-7权重小于0-4权重,保留4-7,调用索引优先队列的changeItem(4,0.37);0-2权重小于2-7权重,保留0-2,不需额外操作;

  1. 不断重复上面的动作,就可以把所有的顶点添加到最小生成树中;

4.2 API设计

类名 PrimMST
构造方法 PrimMST(EdgeWeightedGraph G):根据一个加权无向图,创建最小生成树计算对象
成员方法 1. private void visit(EdgeWeightedGraph G, int v):将顶点v添加到最小生成树中,并且更新数据;
2. public Deque edges():获取最小生成树的所有边;
成员变量 1. private Edge[] edgeTo:索引代表顶点,值表示当前顶点和最小生成树之间的最短边;
2. private double[] distTo:索引代表顶点,值表示当前顶点和最小生成树之间的最短边的权重;
3. private boolean[] marked:索引代表顶点,如果当前顶点已经在树中,则值为true,否则为false;
4. private indexMinPriorityQueue pq:存放树中顶点与非树中顶点之间的有效横切边;

4.3 Java代码演示

import java.util.Deque;
import java.util.LinkedList;public class PrimMST {//Edge数组表示当前顶点和最小生成树之间的最短边private Edge[] edgeTo;//distTo数组表示当前顶点和最小生成树之间的最短边的权重,初始值为无穷大private double[] distTo;//marked数组标识当前顶点是否在最小生成树中,如果在,值为true,如果不在,值为falseprivate boolean[] marked;//存放树中顶点与非树中顶点之间的有效横切边private IndexMinPriorityQueue<Double> pq;//根据一副加权无向图,创建最小生成树计算对象public PrimMST(EdgeWeightedGraph G) {this.edgeTo = new Edge[G.V()];this.distTo = new double[G.V()];for (int i = 0; i < distTo.length; i++) {distTo[i] = Double.POSITIVE_INFINITY;}this.marked = new boolean[G.V()];pq = new IndexMinPriorityQueue<>(G.V());//默认让顶点0进入树中,但0顶点目前没有与树中其他的顶点相连接,因此初始化distTo[0]=0.0distTo[0] = 0.0;//使用顶点0和权重0初始化pqpq.insert(0,0.0);//遍历有效边队列while (!pq.isEmpty()){//找到权重最小的横切边对应的顶点,加入到最小生成树中visit(G,pq.delMin());}}//将顶点v添加到最小生成树中,并且更新数据private void visit(EdgeWeightedGraph G, int v) {//把顶点v添加到最小生成树中marked[v] = true;//更新数据for (Edge e : G.adj(v)) {//获取e边的另外一个顶点(当前顶点是v)int w = e.other(v);//判断另外一个顶点是不是已经在树中,如果在树中,则不做任何处理,如果不在树中,更新数据if (marked[w]){continue;}//如果v-w边e的权重比目前distTo[w]权重小,则需要修正数据if (e.weight() < distTo[w]){//把顶点w距离最小生成树的边修改为eedgeTo[w] = e;//把顶点w距离最小生成树的边的权重修改为e.weight()distTo[w] = e.weight();//如果pq中存储的有效横切边已经包含了w顶点,则需要修正最小索引优先队列w索引关联的权重值if (pq.contains(w)){pq.changeItem(w, e.weight());}else{//如果pq中存储的有效横切边不包含w顶点,则需要向最小索引优先队列中添加v-w和其权重值pq.insert(w,e.weight());}}}}//获取最小生成树的所有边public Deque<Edge> edges() {//创建队列对象Deque<Edge> allEdges = new LinkedList<>();//遍历edgeTo数组,拿到每一条边,如果不为null,则添加到队列中for (int i = 0; i < edgeTo.length; i++) {if (edgeTo[i]!=null){allEdges.offer(edgeTo[i]);}}return allEdges;}
}
public class Test {public static void main(String[] args) {EdgeWeightedGraph g = new EdgeWeightedGraph(8);Edge e1 = new Edge(4,5,0.35); Edge e2 = new Edge(4,7,0.37);Edge e3 = new Edge(5,7,0.28); Edge e4 = new Edge(0,7,0.16);Edge e5 = new Edge(1,5,0.32); Edge e6 = new Edge(0,4,0.38);Edge e7 = new Edge(2,3,0.17); Edge e8 = new Edge(1,7,0.19);Edge e9 = new Edge(0,2,0.26); Edge e10 = new Edge(1,2,0.36);Edge e11 = new Edge(1,3,0.29); Edge e12 = new Edge(2,7,0.34);Edge e13 = new Edge(6,2,0.40); Edge e14 = new Edge(3,6,0.52);Edge e15 = new Edge(6,0,0.58); Edge e16 = new Edge(6,4,0.93);g.addEdge(e1);g.addEdge(e2);g.addEdge(e3);g.addEdge(e4);g.addEdge(e5);g.addEdge(e6);g.addEdge(e7);g.addEdge(e8);g.addEdge(e9);g.addEdge(e10);g.addEdge(e11);g.addEdge(e12);g.addEdge(e13);g.addEdge(e14);g.addEdge(e15);g.addEdge(e16);//构建PrimMST对象PrimMST mst = new PrimMST(g);//获取最小生成树的边Deque<Edge> edges = mst.edges();//打印输出for (Edge edge : edges) {System.out.println(edge.either() + "-" + edge.other(edge.either()) + ":" + edge.weight());}}
}
1-7:0.19
0-2:0.26
2-3:0.17
4-5:0.35
5-7:0.28
6-2:0.4
0-7:0.16

注:

  • 需要用到的类Edge、EdgeWeightedGraph,可在加权无向图的定义及实现(Java)_sc179的博客-CSDN博客中找到;
  • 类IndexMinPriority可在索引最小优先队列的实现_sc179的博客-CSDN博客中找到;

5 Kruskal算法

Kruskal算法是另外一种计算加权无向图的最小生成树的算法。

5.1 算法步骤

  1. 使用最小优先队列PriorityQueue< Edge > pq = new PriorityQueue<>();存储所有边,每次使用pq.delMin()取出权重最小的边,并得到该边关联的两个顶点v和w;
  2. 通过并查集UF_Tree_Weighted uf = new UF_Tree_Weighted(G.V()),调用uf.connect(v,w)判断v和w是否已经连通,如果连通,则证明这两个顶点在同一棵树中,那么就不能把这条边添加到最小生成树中,因为在最小生成树的任意两个顶点上添加一条边,都会形成环,而最小生成树不能有环的存在;如果不连通,则通过uf.connect(v,w)把顶点v所在的树和顶点w所在的树合并成一棵树,并把这条边加入到Deque< Edge > mst队列中;
  3. 直到mst队列中含有V-1条边,最终mst中存储的就是最小生成树的所有边;

5.1 API设计

类名 KruskalMST
构造方法 KruskalMST(EdgeWeightedGraph G):根据一个加权无向图,创建最小生成树计算对象
成员方法 1. public Deque edges():获取最小生成树的所有边;
成员变量 1. private Deque mst:保存最小生成树的所有边;
2. private UF_Tree_Weighted uf:索引代表顶点,使用uf.connect(v,w)可以判断顶点v和顶点w是否在同一棵树中,使用uf.union(v,w)可以把顶点v所在的树和顶点w所在的树合并;
3. private MinPriorityQueue pq:存储图中所有的边,使用最小优先队列,对边按照权重进行排序;






5.2 Java代码演示

import java.util.Deque;
import java.util.LinkedList;
import java.util.PriorityQueue;public class KruskalMST {//使用队列保存最小生成树的所有边private Deque<Edge> mst;//使用并查集,索引代表顶点,uf.connect(v,w)可以判断顶点v和顶点w是否在同一颗树中,使用uf.union(v,w)可以把顶点v所在的树和顶点w所在的树合并private UF_Tree_Weighted uf;//存储图中所有的边,使用最小优先队列,对边按照权重排序private PriorityQueue<Edge> pq;//根据一副加权无向图,创建最小生成树计算对象public KruskalMST(EdgeWeightedGraph G) {//初始化mstthis.mst = new LinkedList<>();//初始化ufthis.uf = new UF_Tree_Weighted(G.V());//初始化pqthis.pq = new PriorityQueue<>();//把图中所有的边存储到pq中for (Edge e : G.edges()) {pq.offer(e);}//遍历pq队列,拿到最小权重的边,进行处理while(!pq.isEmpty() && mst.size() < G.V()-1){//找到权重最小的边Edge e = pq.poll();//找到该边的两个顶点int v = e.either();int w = e.other(v);//判断这两个顶点是否已经在同一颗树中,如果在同一颗树中,则不对该边做处理,如果不在一棵树中,则让这两个顶点属于的两棵树合并成一棵树if (uf.connected(v,w)){continue;}uf.union(v,w);//让边e进入到mst队列中mst.offer(e);}}//获取最小生成树的所有边public Deque<Edge> edges() {return mst;}
}
import java.util.Deque;public class Test {public static void main(String[] args) {EdgeWeightedGraph g = new EdgeWeightedGraph(8);Edge e1 = new Edge(4,5,0.35); Edge e2 = new Edge(4,7,0.37);Edge e3 = new Edge(5,7,0.28); Edge e4 = new Edge(0,7,0.16);Edge e5 = new Edge(1,5,0.32); Edge e6 = new Edge(0,4,0.38);Edge e7 = new Edge(2,3,0.17); Edge e8 = new Edge(1,7,0.19);Edge e9 = new Edge(0,2,0.26); Edge e10 = new Edge(1,2,0.36);Edge e11 = new Edge(1,3,0.29); Edge e12 = new Edge(2,7,0.34);Edge e13 = new Edge(6,2,0.40); Edge e14 = new Edge(3,6,0.52);Edge e15 = new Edge(6,0,0.58); Edge e16 = new Edge(6,4,0.93);g.addEdge(e1);g.addEdge(e2);g.addEdge(e3);g.addEdge(e4);g.addEdge(e5);g.addEdge(e6);g.addEdge(e7);g.addEdge(e8);g.addEdge(e9);g.addEdge(e10);g.addEdge(e11);g.addEdge(e12);g.addEdge(e13);g.addEdge(e14);g.addEdge(e15);g.addEdge(e16);//构建PrimMST对象KruskalMST mst = new KruskalMST(g);//获取最小生成树的边Deque<Edge> edges = mst.edges();//打印输出for (Edge edge : edges) {System.out.println(edge.either() + "-" + edge.other(edge.either()) + ":" + edge.weight());}}
}
0-7:0.16
2-3:0.17
1-7:0.19
0-2:0.26
5-7:0.28
4-5:0.35
6-2:0.4

注:

  • 需要用到的类Edge、EdgeWeightedGraph,可在加权无向图的定义及实现(Java)_sc179的博客-CSDN博客中找到;
  • 类UF_Tree_Weighted可在并查集详解:UF——UF_Tree——UF_Tree_Weighted逐步优化_sc179的博客-CSDN博客中找到;

加权无向图与最小生成树(Prim算法和Kruskal算法)相关推荐

  1. 最小生成树-Prim算法和Kruskal算法

    转载自:https://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html Prim算法 1.概览 普里姆算法(Prim算法),图论 ...

  2. 数据结构与算法—最小生成树(Prim算法和Kruskal算法算法详解)

    前言 在数据结构与算法的图论中,(生成)最小生成树算法是一种常用并且和生活贴切比较近的一种算法.但是可能很多人对概念不是很清楚.我们看下百度百科对于最小生成树定义: 一个有 n 个结点的连通图的生成树 ...

  3. 生成随机数放入整型数组怎么判断有没有重复_图的应用(1)-连通图的最小生成树(Prim算法和Kruskal算法)...

    连通图的生成树: 是一个极小的连通图,它含有图中全部的N个顶点,但是只足以构成一颗树的N-1条边. 必须满足三个条件: 图是连通图: 图中包含了N个结点 图中边的数量等于N-1条. 连通图生成树的判断 ...

  4. 最小生成树———prim算法和kruskal算法详解

    最小生成树之prim算法(转载出处) 边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权. 最小生成树(MST):权值最小的生成树. 生成树和最小生成树的应用: ...

  5. 最小生成树Prim算法和Kruskal算法

    https://www.cnblogs.com/JoshuaMK/p/prim_kruskal.html 转载于:https://www.cnblogs.com/DixinFan/p/9225105. ...

  6. 求的带权图最小生成树的Prim算法和Kruskal算法

    求的带权图最小生成树的Prim算法和Kruskal算法 最小生成树的概念 最小生成树其实是最小权重生成树的简称. 一个连通图可能有多个生成树.当图中的边具有权值时,总会有一个生成树的边的权值之和小于或 ...

  7. 作业1-采用Prim算法和Kruskal算法构造最小生成树

    采用Prim算法和Kruskal算法构造最小生成树 实验报告 1.问题 2.解析 (1)Prim算法 (2)Kruskal算法 3.设计 (1)Prim算法 (2)Kruskal算法 4.分析 (1) ...

  8. 最小生成树之Prim算法和Kruskal算法

    一个连通图可能有多棵生成树,而最小生成树是一副连通加权无向图中一颗权值最小的生成树,它可以根据Prim算法和Kruskal算法得出,这两个算法分别从点和边的角度来解决. Prim算法 输入:一个加权连 ...

  9. 【最小生成树】Prim算法和Kruskal算法的区别对比

    Prim算法和Kruskal算法都是从连通图中找出最小生成树的经典算法- 从策略上来说,Prim算法是直接查找,多次寻找邻边的权重最小值,而Kruskal是需要先对权重排序后查找的- 所以说,Krus ...

最新文章

  1. numpy随机生成数组
  2. matlab、python使用小方法收集
  3. Failed to install VS Code update.
  4. Redis 内存压缩实战
  5. centos 程序 mysql数据库文件位置,CentOS 更改MySQL数据库目录位置
  6. android studio导入jar包
  7. Jenkins常用插件
  8. join实例 oracle_oracle中join联合查询
  9. 两个三维向量叉积_线性代数的本质08 叉积
  10. java自行车起源_自行车的起源
  11. B - 最短路径问题
  12. mt950报文解析_MT700报文升级内容逐项解析
  13. 调节汉化版Eclipse的字体大小
  14. Android启动模式分析
  15. 360极速浏览器的兼容模式
  16. android studio 2.2 preview4,Android Studio变黄了,2.2 Preview 4 发布
  17. 【VS 生成exe程序添加程序图标】
  18. Java学习笔记:公司账目流水表生成报表1
  19. texture中的 anisotropy属性,纹理的各向异性
  20. 苹果手机用android,用习惯了苹果手机还能从苹果换到安卓吗?

热门文章

  1. js按钮绑定点击事件
  2. 如何设计安全可靠的开放接口---对请求参加密保护
  3. 工欲善其事,必先利其器--vim-tips
  4. Python热门吗 前景怎么样
  5. 视频营销3-视频设备清单
  6. 有趣的小项目:半个指头大的收音机制作成功 单片机+RDA5807源程序
  7. MySQL DML数据库操作
  8. Vue 3.0父子组件通信
  9. c语言常量2l是什么,2017年计算机二级c语言题库
  10. 记一次计算机课作文,关于记一次课堂游戏作文(精选5篇)