最小生成树问题


前提

在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集且为无循环图,使得联通所有结点的的 w(T) 最小,则此 T 为 G 的最小生成树。
最小生成树其实是最小权重生成树的简称。
先回忆回忆什么是树、什么是图、什么是最小生成树。

1 描述最小生成树问题算法的输入、输出

1.1 最小生成树问题算法的输入:由实际生活中的点和边构成的图,如基电站和电缆线路、景点和公路等抽象化出来的图;

1.2 最小生成树问题算法的输出:由输入的图经过Prim算法或者Kruskal 算法等处理最终得到一个各条边权值之和最小的树,而得到的这棵树叫做最小生成树,该生成树往往是修公路的费用最小化的修路方式、修电站的通信的电线的费用最小化的修造方式。

1.3 最小生成树要解决的两个问题:

(1)尽可能选取权值小的边,但不能构成回路;

(2)选取n-1条恰当的边以连接网的n个顶点。

1.4 构成网的一棵最小生成树,即:在e条带权的边中选取n-1条边(不构成回路),使“权值之和”为最小。


2 普里姆算法(Prim)

2.1 本质:加点法

2.2 基本思想:

取图中任意一个顶点V作为生成树的根,之后往生成树上添加新的顶点W。在添加的顶点W和已经在生成树上的顶点V之间必定存在一条边,该边的权值在所有连通项点V和W之间的边中取值为最小。之后继续往生成树上添加顶点,直至生成树上含有n个顶点为止。

2.3 具体做法:

在生成树的构造过程中,让连通图中n个顶点分属两个集合:已经加入到生成树上的顶点集合U和(原连通图上)还没加入到生成树上的顶点集合V-U;每次在V-U集合里选中一个和生成树上某一个顶点连线中权值最小的,将此顶点加入到生成树里。

2.4 实例:

(1)如下带权无向连通图1:

图1

(2)从顶点a开始加点:

说明:可能考虑的边的权值,这一列的数据,标志为红色是指这权值为X1的边的两个顶点为U集合;标志为蓝色是指这权值为X2的边再次被遇到,但这边不能在本次最小生成树;为空,则表示Prim算法结束。

(3)最终得到最小生成树,如下图中的用红色边连通的顶点所构成的树:

(4)主要代码(用java实现):

public static void onChangeVertex(Vertex vertex) {visitedVertexs.add(vertex); //添加初始节点,作为默认的开始节点leftedVertexs.remove(vertex);
}public static Vertex findOneVertex(Graph g) {int minValue = Integer.MAX_VALUE;Vertex findVertex = new Vertex();Edge findEdge = new Edge();for(int i=0;i<visitedVertexs.size();i++) {for(int j=0;j<leftedVertexs.size();j++) {Vertex v1 = visitedVertexs.get(i);Vertex v2 = leftedVertexs.get(j); //获取两个顶点的名称for(int k=0;k<g.edge.length;k++) {String startName = g.edge[k].startVertex.vName;String endName = g.edge[k].endVertex.vName;if((v1.vName.equals(startName) && v2.vName.equals(endName)) ||(v1.vName.equals(endName) && v2.vName.equals(startName))){if(g.edge[k].weight < minValue) {findEdge = g.edge[k];minValue = g.edge[k].weight;if(leftedVertexs.contains(v1)){ //会调用对象的equals方法比较对象,需重写equals方法findVertex = v1;}else if(leftedVertexs.contains(v2)){findVertex = v2;}}}}}}g.minWeight+= minValue;searchEdges.add(findEdge);return findVertex;
}public static void prim(Graph g) {while(leftedVertexs.size()>0){ //直到剩余节点集为空时结束循环Vertex findVertex = findOneVertex(g);onChangeVertex(findVertex);}System.out.print("\n最短路径包含的边: ");for(int i=0;i<searchEdges.size();i++) {System.out.print("("+searchEdges.get(i).startVertex.vName+","+searchEdges.get(i).endVertex.vName+")"+" ");}System.out.println("\n最短路径长度: "+g.minWeight);
}

(5)测试结果截图(截图过小,请您见谅):


3 克鲁斯卡尔算法(Kruskal)

3.1 本质:加边法。为使最小生成树上的边的权值之和达到最小,则应使生成树中的每条边的权值尽可能地小。

3.2 基本思想:

先构造一个只含有n个顶点的子图SG,即只选原图中的所有的顶点n作为原图的子图,然后从权值最小的边开始(一条一条地分别加入,加入的过程只要保证子图不产生回路就可以,因为我们最终要找到的生成树是一棵树,所以它是不能有回路的,那么在这个加边的过程中只要保证这一点就可以,因为我们每次都要选权值最小的,只要不产生回路就满足了最小生成树的条件,直到最后这n-1条边全部加入完成,这个算法就可结束),若它的添加不能使SG中产生回路,则在SG上加上这条边,如此重复,直到加上 n-1 条边为止。

3.3 实例:

(1)如下带权无向连通图2:

图2

(2)加边过程(自个儿花时间做PPT,再截图):

提示:从上往下看,最后一张为红色边加顶点构成最小生成树。







(3)代码(C,仅供参考):

#include <stdio.h>
#include <stdlib.h>
#define Max 50typedef struct road *Road;
typedef struct road
{int a , b;int w;
}road;typedef struct graph *Graph;
typedef struct graph
{int e , n;Road data;
}graph;Graph initGraph(int m , int n)
{Graph g = (Graph)malloc(sizeof(graph));g->n = m;g->e = n;g->data = (Road)malloc(sizeof(road) * (g->e));return g;
}void create(Graph g)
{int i;for(i = 1 ; i <= g->e ; i++){int x , y, w;scanf("%d %d %d",&x,&y,&w);if(x < y){g->data[i].a = x;g->data[i].b = y;} else{g->data[i].a = y;g->data[i].b = x;}g->data[i].w = w;}
}int getRoot(int v[], int x)
{while(v[x] != x){x = v[x];}return x;
}//这里没有用到效率更高的堆排序
void sort(Road data, int n)
{int i , j;for(i = 1 ; i <= n-1 ; i++){for(j = 1 ; j <= n-i ; j++){if(data[j].w > data[j+1].w){road t = data[j];data[j] = data[j+1];data[j+1] = t;}}}
}int Kruskal(Graph g)
{int sum = 0;//并查集int v[Max];int i;//初始化步骤for(i = 1 ; i <= g->n ; i++){v[i] = i;}sort(g->data , g->e);//mainfor(i = 1 ; i <= g->e ; i++){int a , b;a = getRoot(v,g->data[i].a);b = getRoot(v,g->data[i].b);if(a != b){v[a] = b;sum += g->data[i].w;}}return sum;
}

3.4 最后温馨提示:

(一)图的生成树不唯一,从不同的顶点出发进行遍历,可以得到不同的生成树;

(二)即使从相同的顶点出发,在选择最小边时,可能有多条同样的边可选,此时任选其一;


4 最小生成树问题的代码解决

4.1 将通信网络抽象为无向图,将各个基站抽象为图的顶点,将预计可能要修的线路抽象为图的边,将修造各条线路的费用抽象为图的边的权值,线路便宜即是边的权值小,线路贵即边的权值大,最便宜的连通线路(含费用的意义)加基站构成最小生成树。

4.2 进行Java代码(Prim算法)实现:

(1)代码(java):

package JavaTestBag;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;/** 最小生成树(普里姆算法(Prim算法))简单版*/// 顶点类Vertex
class Vertex{String vName; //顶点的名称@Overridepublic boolean equals(Object obj) {if(obj instanceof Vertex){Vertex vertex = (Vertex)obj;return this.vName.equals(vertex.vName);}return super.equals(obj);}
}// 边类Edge
class Edge{Vertex startVertex;Vertex endVertex;int weight;
}// 图的存储结构
class Graph{Vertex[] vertex; //顶点集Edge[] edge; //边集int minWeight; //最短路径
}public class MinTreePrimer {private static List<Vertex> visitedVertexs,leftedVertexs; //分别为添加到集合U中的节点集和剩余的集合V中的节点集private static List<Edge> searchEdges;//初始化图的信息public static void initGraph(Graph g) {visitedVertexs = new ArrayList<Vertex>();leftedVertexs = new ArrayList<Vertex>();searchEdges = new ArrayList<Edge>();Scanner sc = new Scanner(System.in);System.out.print("输入基站数: ");int vertexNumber = sc.nextInt();System.out.print("请输入预计可能要修的线路数: ");int edgeNumber = sc.nextInt();String[] allVertex = new String[vertexNumber];String[] allEdge = new String[edgeNumber];System.out.println("=================================");System.out.println("请输入各个基站的代号(如:A):");Scanner scanner = new Scanner(System.in);for(int i=0;i<vertexNumber;i++){System.out.print("基站"+(i+1)+":");allVertex[i] = scanner.nextLine();}System.out.println("=================================");for(int i=0;i<edgeNumber;i++){System.out.print("输入线路(Vi,Vj)中的基站名称和费用W(如:A B 7): ");allEdge[i] = scanner.nextLine();}g.vertex = new Vertex[allVertex.length];g.edge = new Edge[allEdge.length];g.minWeight = 0;for(int i=0;i<allVertex.length;i++) {g.vertex[i] = new Vertex();g.vertex[i].vName = allVertex[i];leftedVertexs.add(g.vertex[i]); //初始化剩余点集合}for(int i=0;i<allEdge.length;i++) {g.edge[i] = new Edge();g.edge[i].startVertex = new Vertex();g.edge[i].endVertex = new Vertex();String edgeInfo[] = allEdge[i].split(" ");g.edge[i].startVertex.vName = edgeInfo[0];g.edge[i].endVertex.vName = edgeInfo[1];g.edge[i].weight = Integer.parseInt(edgeInfo[2]);}}public static void onChangeVertex(Vertex vertex) {visitedVertexs.add(vertex); //添加初始节点,作为默认的开始节点leftedVertexs.remove(vertex);}public static Vertex findOneVertex(Graph g) {int minValue = Integer.MAX_VALUE;Vertex findVertex = new Vertex();Edge findEdge = new Edge();for(int i=0;i<visitedVertexs.size();i++) {for(int j=0;j<leftedVertexs.size();j++) {Vertex v1 = visitedVertexs.get(i);Vertex v2 = leftedVertexs.get(j); //获取两个顶点的名称for(int k=0;k<g.edge.length;k++) {String startName = g.edge[k].startVertex.vName;String endName = g.edge[k].endVertex.vName;if((v1.vName.equals(startName) && v2.vName.equals(endName)) ||(v1.vName.equals(endName) && v2.vName.equals(startName))){if(g.edge[k].weight < minValue) {findEdge = g.edge[k];minValue = g.edge[k].weight;if(leftedVertexs.contains(v1)){ //会调用对象的equals方法比较对象,需重写equals方法findVertex = v1;}else if(leftedVertexs.contains(v2)){findVertex = v2;}}}}}}g.minWeight+= minValue;searchEdges.add(findEdge);return findVertex;}public static void prim(Graph g) {while(leftedVertexs.size()>0){ //直到剩余节点集为空时结束循环Vertex findVertex = findOneVertex(g);onChangeVertex(findVertex);}System.out.print("\n在实现连通各个基站、所花费的费用最低的情况下,要修的线路: ");for(int i=0;i<searchEdges.size();i++) {System.out.print("("+searchEdges.get(i).startVertex.vName+","+searchEdges.get(i).endVertex.vName+")"+" ");}System.out.println("\n在实现连通各个基站、所花费的费用最低的情况下,要修的线路的费用: "+g.minWeight);}public static void main(String[] args) {Graph g = new Graph();initGraph(g);onChangeVertex(g.vertex[0]);prim(g);}
}

(2)测试结果图如下:


参考文献

[1] Jon Kleinberg,Eva Tardos 著,张立昂,屈婉玲 译. 算法设计. .北京:清华大学出版社,2007.3
[2] Eric,Lehaman. 计算机科学中的数学-信息与智能时代的必修课. 北京:电子工业出版社

最小生成树问题的算法笔记相关推荐

  1. 【数据结构笔记29】最小生成树问题:Prim算法与Kruskal算法

    本次笔记内容: 8.1.1 Prim算法 8.1.2 Kruskal算法 文章目录 最小生成树问题 什么是最小生成树(Minimum Spanning Tree) 贪心算法 Prim算法 Kruska ...

  2. 使用贪心算法解决最小生成树问题。

    使用贪心算法解决最小生成树问题. #include<iostream> #include<algorithm> using namespace std; const int M ...

  3. 【算法】Kruskal算法(解决最小生成树问题) 含代码实现

    Kruskal算法和Prim算法一样,都是求最小生成树问题的流行算法. 算法思想: Kruskal算法按照边的权值的顺序从小到大查看一遍,如果不产生圈或者重边,就把当前这条边加入到生成树中. 算法的正 ...

  4. 最小生成树 kruskal_使用Kruskal算法求解Java最小生成树问题

    最小生成树 kruskal In Electronic Circuit we often required less wiring to connect pins together. We can m ...

  5. 数据结构与算法-Prim算法解析与解决修路最小生成树问题

    文章目录 简介 Prim算法 最小生成树 应用场景 问题描述 思路分析 代码实现 简介 Prim算法 ​ 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索 ...

  6. 【数据结构】最小生成树问题(Prim算法和Kruskal算法)

    相关概念 连通图与它的生成树 连通图的生成树是包含图中全部顶点的一个极小连通子图.若图的顶点数为n,则它的生成树含有n-1条边.一个连通图可能拥有多个生成树. 最小生成树(Minimum-Spanni ...

  7. 并查集-算法详解及例题(最小生成树问题)

    一.并查集的概念: 并查集(Union-find Sets)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题.一些常见的用途有求连通子图.求最小生成树的 Kruskal 算法和求 ...

  8. 算法笔记知识点整理大全

    每次刷题都觉得自己吃了知识点不全,基础不牢固的亏,刷题的时候目标也不明确,于是看完了算法笔记并把知识点归纳了一下,当然直接看书会更加详细,这个归纳只是学习时加深印象以及方便自己之后回顾而已:之后刷题大 ...

  9. 《算法笔记》——笔记

    算法笔记 胡凡 曾磊 主编 机械工业出版社 文章目录 算法笔记 C/C++快速入门 提醒 memset sscanf与sprintf 引用 浮点数的比较 圆周率 复杂度 黑盒测试 入门篇(1)--入门 ...

最新文章

  1. Date类(java.util)和SimpleDateFormat类(java.text)
  2. vue2.0中文网站2.0
  3. 查看Unix/Linux的CPU个数和内存大小,系统位数(转载)
  4. mysql 用命令行复制表数据到新表
  5. 还有Html.EditorFor和Html.Html.TextBox到底差什么呢
  6. SAP UI5 Dialog wrong location
  7. 微信批量扫码进群系统
  8. 15天助你掌握问卷统计与Spss实战
  9. RGB和CMYK配色表
  10. 为什么越来越多的人选择FUP T10S系列超声波探伤仪
  11. matlab张正友程序,张正友标定程序—MATLAB
  12. 苹果VR大业再添一员大将Spaces,创始人来自梦工厂!
  13. Maya 基础教程 、 基础操作讲解
  14. css实现跑马灯效果
  15. 51单片机--外部中断
  16. 【Windows】win7虚拟机安装VMware Tools
  17. pandas级联与合并
  18. 基于Linux的kfifo移植到STM32(支持os的互斥访问)
  19. 1. A星算法解决修道士与野人问题
  20. 命好啊,只能说您遇到仁义的领导了

热门文章

  1. 思科wlc产品文档_案例:教你在思科官网查找文档解决你的问题
  2. 实验室功放三极,669,649,TIP122,127,5551,5401引脚和NE5532
  3. Vue进阶(二):Vue 项目文件结构介绍
  4. 工作一年多对于前后端联调些许记录
  5. 彻底删除迅雷影音(2022年12月16-17日)
  6. .NET 程序员必备工具下载(2)(完结)
  7. 在线招聘:庞大市场,“渺小”平台
  8. python合法的变量名有哪些_中国大学MOOC: 以下不是Python中合法变量名的是______。...
  9. 原理图库元件符号之排针元件符号的创建
  10. 【毕业设计/Matlab项目】调幅AM/调频FM/DPSK/FSK的调制解调matlab界面