一:最小生成树

(一)定义

我们把构造连通网的最小代价生成树称为最小生成树或给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树.

(二)什么是最小生成树?

1.是一棵树

1)无回路
2)N个顶点,一定有N-1条边

2.是生成树

1)包含全部顶点
2)N-1条边都在图中

3.边的权重和最小

(三)案例说明

在实际生活中,我们常常碰到类似这种一类问题:如果要在n个城市之间建立通信联络网,
则连通n个城市仅仅须要n-1条线路。这时。我们须要考虑这样一个问题。怎样在最节省经费前提下建立这个通信网.换句话说,我们须要在这n个城市中找出一个包括全部城市的连通子图,使得其全部边的经费之和最小. 这个问题能够转换为一个图论的问题:图中的每一个节点看成是一个城市,节点之间的无向边表示修建该路的经费。即每条边都有其对应的权值,而我们的目标是挑选n-1条边使全部节点保持连通。而且要使得经费之和最小.这里存在一个显而易见的事实是: 最优解中必定不存在循环(可通过反证法证明). 因此。最后找出的包括全部城市的连通子图必定没有环路。这样的连通且没有环路的连通图就简称为树。而在一个连通图中删除全部的环路而形成的树叫做该图的生成树.对于城市建立通信连通网。须要找出的树由于具有最小的经费之和。因此又被称为最小生成树(Minimum Cost Spanning Tree),简称MST.

(四)求最小生成树的算法

(1) 普里姆算法

图的存贮结构采用邻接矩阵.此方法是按各个顶点连通的步骤进行,需要用一个顶点集合,开始为空集,以后将以连通的顶点陆续加入到集合中,全部顶点加入集合后就得到所需的最小生成树 .

(2) 克鲁斯卡尔算法

图的存贮结构采用边集数组,且权值相等的边在数组中排列次序可以是任意的.该方法对于边相对比较多的不是很实用,浪费时间.

二:贪心算法

1.什么是贪?

每一步都要最好(只看下一步)

2.什么是好?

权重最小的边

3.需要约束

1.只能用图里有的边
2.只能正好用掉N-1条边
3.不能有回路

三:普里姆算法(稠密图)

(一)定义

对于一个带权的无向连通图,其每个生成树所有边上的权值之和可能不同,我们把所有边上权值之和最小的生成树称为图的最小生成树。
普里姆算法是以其中某一顶点为起点,逐步寻找各个顶点上最小权值的边来构建最小生成树。
其中运用到了回溯,贪心的思想。

(二)算法思路

设图G=(V,E),U是顶点集V的一个非空子集。假设(u,v)是一条具有最小权值的边。当中u∈U,v∈V-U,

则必存在一棵包括边(u,v)的最小生成树.

上述的性质能够通过反证法证明。假设(u,v)不包括在G的最小生成树T中。那么,T的路径中必定存

在一条连通U和V-U的边,假设将这条边以(u,v)来替换,我们将获得一个权重更低的生成树,这与T

是最小生成树矛盾.既然MST满足贪婪选择属性。那么。求解最小生成树的问题就简化了非常多。

总结一下,详细的步骤大概例如以下:

1.构建一棵空的最小生成树T。并将全部节点赋值为无穷大.
2.任选一个节点放入T。另外一个节点集合为V-T.
3.对V-T中节点的赋值进行更新(因为此时新增加一个节点,这些距离可能发生变化)
4.从V-T中选择赋值最小的节点,增加T中
5.假设V-T非空,继续步骤3~5,否则算法终结

(三)步骤模拟

原图:

以上图G4为例,来对普里姆进行演示(从第一个顶点A开始通过普里姆算法生成最小生成树)。

初始状态:V是所有顶点的集合,即V={A,B,C,D,E,F,G};U和T都是空!

第1步:将顶点A加入到U中。 
    此时,U={A}。 
第2步:将顶点B加入到U中。 
    上一步操作之后,U={A}, V-U={B,C,D,E,F,G};因此,边(A,B)的权值最小。将顶点B添加到U中;此时,U={A,B}。 
第3步:将顶点F加入到U中。 
    上一步操作之后,U={A,B}, V-U={C,D,E,F,G};因此,边(B,F)的权值最小。将顶点F添加到U中;此时,U={A,B,F}。 
第4步:将顶点E加入到U中。 
    上一步操作之后,U={A,B,F}, V-U={C,D,E,G};因此,边(F,E)的权值最小。将顶点E添加到U中;此时,U={A,B,F,E}。 
第5步:将顶点D加入到U中。 
    上一步操作之后,U={A,B,F,E}, V-U={C,D,G};因此,边(E,D)的权值最小。将顶点D添加到U中;此时,U={A,B,F,E,D}。 
第6步:将顶点C加入到U中。 
    上一步操作之后,U={A,B,F,E,D}, V-U={C,G};因此,边(D,C)的权值最小。将顶点C添加到U中;此时,U={A,B,F,E,D,C}。 
第7步:将顶点G加入到U中。 
    上一步操作之后,U={A,B,F,E,D,C}, V-U={G};因此,边(F,G)的权值最小。将顶点G添加到U中;此时,U=V。

此时,最小生成树构造完成!它包括的顶点依次是:A B F E D C G

(三)算法实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>#define MAXVEX 100    //最大顶点数
#define INFINITY 65535    //用0表示∞typedef char VertexType;    //顶点类型,字符型A,B,C,D...
typedef int EdgeType;    //边上权值类型10,15,...//邻接矩阵结构
typedef struct
{VertexType vers[MAXVEX];    //顶点表EdgeType arc[MAXVEX][MAXVEX];    //邻接矩阵,可看作边表int numVertexes, numEdges;    //图中当前的顶点数和边数
}MGraph;void CreateMGraph(MGraph* G);
void showGraph(MGraph G);void MiniSpanTree_prim(MGraph G);    //Prim算法生成最小生成树//Prim算法生成最小生成树
void MiniSpanTree_prim(MGraph G)
{int min, i, j, k;int adjvex[MAXVEX];        //保存相关顶点下标int lowcost[MAXVEX];    //保存相关顶点间边的权值lowcost[0] = 0;    //初始化第一个权值为0,即将v0加入生成树//lowcost的值为0表示此下标的顶点已经加入生成树adjvex[0] = 0;    //初始化第一个顶点下标为0for (i = 1; i < G.numVertexes;i++){lowcost[i] = G.arc[0][i];    //将v0顶点与之有关的边的权值都存放入权值数组adjvex[i] = 0;    //初始化都为v0的下标
    }for (i = 1; i < G.numVertexes;i++){min = INFINITY;j = 1;k = 0;while (j<G.numVertexes){if (lowcost[j]!=0&&lowcost[j]<min){//如果权值不为0且权值小于minmin = lowcost[j];    //则让当前权值成为最小值k = j;    //将当前最小值的下标存放k
            }j++;}printf("(%d,%d)", adjvex[k], k);    //打印当前顶点边中权值最小边lowcost[k] = 0;//将当前顶点的权值置为0,表示此顶点已经完成任务//和上面做了几乎一样的操作,就是更新权值for (j = 1; j < G.numVertexes;j++)    //循环所有顶点,因为我们已经确认第一个放入的0,所有我们循环可以省去0
        {if (lowcost[j] != 0 && G.arc[k][j]<lowcost[j])    {//若下标为k顶点个边权值小于此前顶点,就不会加入生成树权值lowcost[j] = G.arc[k][j];    //将较小权值存入lowcostadjvex[j] = k;        //将下标为k的顶点存入adjvex
            }}}
}int main()
{MGraph MG;CreateMGraph(&MG);showGraph(MG);MiniSpanTree_prim(MG);system("pause");return 0;
}void CreateMGraph(MGraph* G)
{int i, j, k, w;G->numVertexes = 9;G->numEdges = 15;//读入顶点信息G->vers[0] = 'A';G->vers[1] = 'B';G->vers[2] = 'C';G->vers[3] = 'D';G->vers[4] = 'E';G->vers[5] = 'F';G->vers[6] = 'G';G->vers[7] = 'H';G->vers[8] = 'I';//getchar();    //可以获取回车符for (i = 0; i < G->numVertexes; i++)for (j = 0; j < G->numVertexes; j++)G->arc[i][j] = INFINITY;    //邻接矩阵初始化
G->arc[0][1] = 10;G->arc[0][5] = 11;G->arc[1][2] = 18;G->arc[1][6] = 16;G->arc[1][8] = 12;G->arc[2][3] = 22;G->arc[2][8] = 8;G->arc[3][4] = 20;G->arc[3][7] = 16;G->arc[3][6] = 24;G->arc[3][8] = 21;G->arc[4][5] = 26;G->arc[4][7] = 7;G->arc[5][6] = 17;G->arc[6][7] = 19;for (k = 0; k < G->numVertexes; k++)    //读入numEdges条边,建立邻接矩阵
    {for (i = k; i < G->numVertexes; i++){G->arc[i][k] = G->arc[k][i];    //因为是无向图,所有是对称矩阵
        }}
}void showGraph(MGraph G)
{for (int i = 0; i < G.numVertexes; i++){for (int j = 0; j < G.numVertexes; j++){if (G.arc[i][j] != INFINITY)printf("%5d", G.arc[i][j]);elseprintf("    0");}printf("\n");}
}

(四)普里姆代码分析

//Prim算法生成最小生成树
void MiniSpanTree_prim(MGraph G)
{int min, i, j, k;int adjvex[MAXVEX];        //保存相关顶点下标int lowcost[MAXVEX];    //保存相关顶点间边的权值lowcost[0] = 0;    //初始化第一个权值为0,即将v0加入生成树//lowcost的值为0表示此下标的顶点已经加入生成树adjvex[0] = 0;    //初始化第一个顶点下标为0for (i = 1; i < G.numVertexes;i++){lowcost[i] = G.arc[0][i];    //将v0顶点与之有关的边的权值都存放入权值数组adjvex[i] = 0;    //初始化都为v0的下标
    }for (i = 1; i < G.numVertexes;i++){min = INFINITY;j = 1;k = 0;while (j<G.numVertexes){if (lowcost[j]!=0&&lowcost[j]<min){//如果权值不为0且权值小于minmin = lowcost[j];    //则让当前权值成为最小值k = j;    //将当前最小值的下标存放k
            }j++;}printf("(%d,%d)", adjvex[k], k);    //打印当前顶点边中权值最小边lowcost[k] = 0;//将当前顶点的权值置为0,表示此顶点已经完成任务//和上面做了几乎一样的操作,就是更新权值for (j = 1; j < G.numVertexes;j++)    //循环所有顶点,因为我们已经确认第一个放入的0,所有我们循环可以省去0
        {if (lowcost[j] != 0 && G.arc[k][j]<lowcost[j])    {//若下标为k顶点个边权值小于此前顶点,就不会加入生成树权值lowcost[j] = G.arc[k][j];    //将较小权值存入lowcostadjvex[j] = k;        //将下标为k的顶点存入adjvex
            }}}
}

下面是我们要处理的邻接矩阵

1.创建两个数组,一个存放顶点,一个存放相关顶点间边的权值

int adjvex[MAXVEX];        //保存相关顶点下标
int lowcost[MAXVEX];    //保存相关顶点间边的权值

作用:

adjvex数组:将存放我们左侧的顶点下标
lowcost数组:将存放我们对应顶点的各个边的权值

会利用这两个来打印出我们所需要的最小边

printf("(%d,%d)", adjvex[k], k); //打印当前顶点边中权值最小边   

其中adjvex[k]存放的是我们左侧的弧尾,k是我们找的的邻接点权值最小的弧头

注意:

比如我们要找v3顶点作为弧头的边,那么我们adjvex[3]中将会存放其弧尾,也就是我们的左侧下标

那么我们去找第一条边时,我们只知道与v0相邻顶点间边的权值,并不知道k值,所以我们开始无法知道adjvex[k]=0中k是谁。
但是我们可以在开始对顶点数组adjvex进行初始化,全部初始化为0,就可以解决这个问题

    lowcost[0] = 0;    //初始化第一个权值为0,即将v0加入生成树//lowcost的值为0表示此下标的顶点已经加入生成树adjvex[0] = 0;    //初始化第一个顶点下标为0for (i = 1; i < G.numVertexes;i++){lowcost[i] = G.arc[0][i];    //将v0顶点与之有关的边的权值都存放入权值数组adjvex[i] = 0;    //初始化都为v0的下标}

其中lowcost[0] = 0;是因为我们开始就将v0点放入生成树中,所以要将对应的lowcost[0]设置为0,我们会在后面,将所有的放入生成树中的顶点全部设置为0,但是注意生成树在代码中不是直接出现的

其中lowcost[i] = G.arc[0][i];  是将对应的邻接顶点的权值放入lowcost中

2.循环所有的左侧顶点,获取他们相关的最小邻接边

    for (i = 1; i < G.numVertexes;i++){min = INFINITY;j = 1;k = 0;while (j<G.numVertexes){if (lowcost[j]!=0&&lowcost[j]<min){//如果权值不为0且权值小于minmin = lowcost[j];    //则让当前权值成为最小值k = j;    //将当前最小值的下标存放k
            }j++;}printf("(%d,%d)", adjvex[k], k);    //打印当前顶点边中权值最小边lowcost[k] = 0;//将当前顶点的权值置为0,表示此顶点已经完成任务//和上面做了几乎一样的操作,就是更新权值for (j = 1; j < G.numVertexes;j++)    //循环所有顶点,因为我们已经确认第一个放入的0,所有我们循环可以省去0
        {if (lowcost[j] != 0 && G.arc[k][j]<lowcost[j])    {//若下标为k顶点个边权值小于此前顶点,就不会加入生成树权值lowcost[j] = G.arc[k][j];    //将较小权值存入lowcostadjvex[j] = k;        //将下标为k的顶点存入adjvex
            }}}

其中while循环是获取我们的权值中最小的那个的弧头下标,将会和弧尾组成一条边:

        while (j<G.numVertexes){if (lowcost[j]!=0&&lowcost[j]<min){//如果权值不为0且权值小于minmin = lowcost[j];    //则让当前权值成为最小值k = j;    //将当前最小值的下标存放k
            }j++;}

下面的权值都会存在lowcost中

           

    printf("(%d,%d)", adjvex[k], k);  //可以打印处这条边

我们将找到的这个顶点和上面初始时设置的lowcost一样设置为0,表示已经加入生成树,我们不必去修改他们

    lowcost[k] = 0;//将当前顶点的权值置为0,表示此顶点已经完成任务

下面的for循环和我们之前的for循环更新权值是一致的,但是有些不同

        //和上面做了几乎一样的操作,就是更新权值for (j = 1; j < G.numVertexes;j++)    //循环所有顶点,因为我们已经确认第一个放入的0,所有我们循环可以省去0
        {if (lowcost[j] != 0 && G.arc[k][j]<lowcost[j])    {//若下标为k顶点个边权值小于此前顶点,就不会加入生成树权值lowcost[j] = G.arc[k][j];    //将较小权值存入lowcostadjvex[j] = k;        //将下标为k的顶点存入adjvex
            }}

首先我们做了比较

if (lowcost[j] != 0 && G.arc[k][j]<lowcost[j])   

首先顶点不能及时生成树中的,即lowcost[j]!=0,然后其弧权值需要比原来的权值小才行,因为可能出现原来的权值更加小,这是就要选择原来的边作为新的路径

                lowcost[j] = G.arc[k][j];    //将较小权值存入lowcostadjvex[j] = k;        //将下标为k的顶点存入adjvex

我们更新了权值最新值,会在下一次的循环中再次选取下一个点

四:总结

从指定顶点开始将它加入集合中,然后将集合内的顶点与集合外的顶点所构成的所有边中选取权值最小的一条边作为生成树的边,并将集合外的那个顶点加入到集合中,表示该顶点已连通.再用集合内的顶点与集合外的顶点构成的边中找最小的边,并将相应的顶点加入集合中,如此下去直到全部顶点都加入到集合中,即得最小生成树.

普利姆算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边的数目无关

转载于:https://www.cnblogs.com/ssyfj/p/9488723.html

数据结构(五)图---最小生成树(普里姆算法)相关推荐

  1. (数据结构)图的最小生成树 普里姆算法(Prim)

    假设要在n个城市之间建立通信联络网,每两个城市之间建立线路都需要花费不同大小的经费,则连通n个城市只需要n-1个条线路,最小生成树解决的问题就是:如何在最节省经费的前提下建立这个通信网 也可以理解为: ...

  2. 最小生成树普里姆算法c语言代码,普里姆算法生成最小生成树-C语言描述.doc

    PAGE JIN JINGCHU UNIVERSITY OF TECHNOLOGY <数据结构(C语言描述)> 课程设计 学 院 计算机工程学院 班 级 12级软件技术1班 学 号 201 ...

  3. 最小生成树(普里姆算法【Prim】与克鲁斯卡尔算法【Kruskal】)

    写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站.博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事 ...

  4. 最小生成树——普里姆算法和克鲁斯卡尔算法

    最小生成树 用来解决工程中的代价问题. 一:普里姆算法 具体代码用C语言实现如下: typedef int VRType;typedef char InfoType;#define MAX_NAME ...

  5. 最小生成树 普里姆算法

    题目来源  POJ2349 在介绍什么是最小生成树之前先介绍什么是加权图 在之前已经默认了解了无向图(并查集)以及有向无环图(拓扑排序),那么最小生成树可以理解是一个加权的无向图,那么我们要坐的就是在 ...

  6. 学懂最小生成树(普里姆算法)

    最小生成树,初学者可能会学的感觉云里雾里,不要怕,小编带大家搞懂它! 目录 一.概念介绍 二.实现原理 三.代码实现 一.概念介绍 最小生成树就是在一个图中找到能过一次性穿过所有顶点的最小路径. 上图 ...

  7. USACO 3.1 Agri-Net 最短网络 (最小生成树)(普里姆算法)

    题意 农民约翰被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场.当然,他需要你的帮助.约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场.为了用最 ...

  8. 最小生成树普里姆算法Prim

    目录 前言 一.Prim算法的基本思路 二.代码实现 总结 前言   无论是什么程序都要和数据打交道,一个好的程序员会选择更优的数据结构来更好的解决问题,因此数据结构的重要性不言而喻.数据结构的学习本 ...

  9. 数据结构实验-图-普里姆算法、克鲁斯科尔算法

    数据结构实验-图-普里姆算法.克鲁斯科尔算法 (实验)自定义存储结构,并设计程序完成如下功能: ①创建图:创建带权无向图. ②普里姆算法:采用普里姆算法依次输出最小生成树中各条边. ③克鲁斯科尔算法: ...

  10. prim算法(普里姆算法)详解

    prim算法(普里姆算法)详解 了解了什么是最小生成树后,本节为您讲解如何用普里姆(prim)算法查找连通网(带权的连通图)中的最小生成树. 普里姆算法查找最小生成树的过程,采用了贪心算法的思想.对于 ...

最新文章

  1. OpenAI 开放 GPT-3 微调功能,让开发者笑开了花
  2. 算法61---两个字符串的最小ASCII删除和【动态规划】
  3. Redis持久化-深入理解AOF,RDB
  4. 云重磅 | 没有硬件,苹果发布多款“云服务”;阿里云发布基于公共云的虚拟GPU服务;中国移动首发5G套餐...
  5. 错误: 代理抛出异常错误: java.rmi.server.ExportException: Port already in use: 1099; nested exception is
  6. pycharm是python2.还是3_Pycharm:Python2和3及其的Anaconda的正确设置
  7. Win10应用程序无法正常启动0xc0000142错误的解决方法
  8. 入侵提权过程中猜解linux路径与windows路径,网站路径暴力
  9. 奇点云数据中台技术汇(四)| DataSimba系列之流式计算
  10. 【转】一个女孩的上海5年
  11. 关于DVD的VOB文件的认识
  12. 工作五年,一年内我靠这系列java面试宝典从13K到大厂30K
  13. Swagger2.0
  14. Python爬虫:爬取百度图片(selenium模拟登录,详细注释)
  15. 定时锁定计算机代码bat,批处理 实现定时关机、注销、重启、锁定等功能
  16. Vue电商后台管理系统(1)
  17. 【畅通工程 HDU - 1232 】【并查集模板题】
  18. 微信SDK非微信ipad协议
  19. IntelliJ IDEA 热键冲突
  20. 手机号身份证号隐藏显示

热门文章

  1. 2020华为杯E题——基于灰色预测的大雾能见度预测模型(附代码)
  2. vuejs study
  3. seastar介绍及源码分析
  4. 程序猿转型AI必须知道的几件事!
  5. 基于PanoSim仿真开发平台BSD和RCTA的构思
  6. unable to read local cache ‘C:\\Users\\admin/gensim-data\\information.json‘ during fallback 解决办法
  7. XR21V141x usb转串口芯片驱动添加
  8. 什么样的程序员才能算是一个合格的程序员呢?
  9. C++ list及数组中数字相连输出问题
  10. iamsujie.com活了一年了