文字描述

  用连通网来表示n个城市及n个城市间可能设置的通信线路,其中网的顶点表示城市,边表示两城市之间的线路,赋于边的权值表示相应的代价。对于n个定点的连通网可以建立许多不同的生成树,每一棵生成树都可以是一个通信网。现在,我们要选择这样一个生成树,使总的耗费最少。这个问题就是构造连通网的最小代价生成树(Minimum Cost Spanning Tree: 最小生成树)的问题。一棵生成树的代价就是树上各边的代价之和。

  有多种算法可以构造最小生成树,其他多数都利用的最小生成的MST(minimum spanning tree)性质: 假设N={V, {E}}是一个连通网,U是顶点集V的一个非空子集。若(u,v)是一条具有最小权值(代价)的边,其中u属于U, v属于V-U,则必存在一棵包含边(u,v)的最小生成树。 该性质可以用反证法证明。

现介绍普里姆(Prim)算法是如何利用MST性质求连通图的最小生成树的:

假设N={V,{E}}是连通网,TE是N上最小生成树中边的集合。算法从U={u0} (u0属于V, TE={})开始,重复执行下述操作:在所有u属于U, v属于V-U 的边(u,v)属于E 中找一条代价最小的边(u0,v0)并入集合TE,同时v0并入U,直至U=V为止。

为实现这个算法需附设一个辅助数组closeedge, 以记录从U到V-U具有最小代价的边。对每个属于V-U的顶点vi ,在辅助数组中存在一个相应分量closedge[i-1],它包括两个域:

  1:lowcose:存储该边上的权; 显然closedge[i-1].lowcost = Min{cost(u,vi) | u属于U}

  2:vex: 存储该边依附的U中的顶点。

示意图

算法分析

  在代码实现中的MinSpanTree_PRIM函数中。若网中有n个顶点, 则第一个初始化的循环语句的频度为n,第二个循环语句的频度为n-1;其中第二个循环中有两个内循环:其一是在 closedge[v].lowcost中求最小值,其频度为n-1;其二是重新选择具有最小代价的边,其频度为n。由此,普里姆算法的时间复杂度为n^2, 与网中的边数无关,因此使用适用于求边稠密的网的最小生成树。

代码实现

 #include <stdio.h>
#include <stdlib.h>
#include <string.h> #define DEBUG #ifdef DEBUG
#include <stdarg.h>
#define LOG(args...) _log_(__FILE__, __FUNCTION__, __LINE__, ##args);
void _log_(const char *file, const char *function, int line, const char * format, ...)
{
char buf[] = {};
va_list list;
va_start(list, format);
sprintf(buf, "[%s,%s,%d]", file, function, line);
vsprintf(buf+strlen(buf), format, list);
sprintf(buf+strlen(buf), "\n");
va_end(list);
printf(buf);
}
#else
#define LOG
#endif // DEBUG #define INFINITY 100000 //最大值
#define MAX_VERTEX_NUM 20 //最大顶点数 //---------邻接矩阵的存储结构----------------------------------------- //////////////////////////////////////////////////////////////
// 邻接矩阵作为图的存储结构
//////////////////////////////////////////////////////////////
typedef enum {DG, DN, UDG, UDN} GraphKind; //{有向图,有向网,无向图,无向网}
typedef int VRType;
typedef char VertexType; //顶点类型
typedef struct{
char note[];
}InfoType;
typedef struct ArcCell{
VRType adj; //顶点关系类型:1)对无权图,用1或0表示相邻否;2)对带权图,则为权值类型
InfoType *info; //该弧相关信息的指针
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct{
VertexType vexs[MAX_VERTEX_NUM]; //顶点向量
AdjMatrix arcs; //邻接矩阵
int vexnum, arcnum; //图的当前顶点数和弧数
GraphKind kind; //图的种类标志
}MGraph; //---------采用邻接矩阵创建无向网----------------------------------------- //////////////////////////////////////////////////////////////
// 若G中存在顶点u,则返回该顶点在图中位置;否则返回-1。
//////////////////////////////////////////////////////////////
int LocateVex(MGraph G, VertexType v){
int i = ;
for(i=; i<G.vexnum; i++){
if(G.vexs[i] == v){
return i;
}
}
return -;
} //////////////////////////////////////////////////////////////
// 采用数组表示法(邻接矩阵),构造无向网
//////////////////////////////////////////////////////////////
int CreateUDN(MGraph *G)
{
printf("\n创建一个无向网(带权):\n");
int i = , j = , k = , IncInfo = ;
int v1 = , v2 = , w = ;
char tmp[] = {};
printf("输入顶点数,弧数,其他信息标志位: ");
scanf("%d,%d,%d", &G->vexnum, &G->arcnum, &IncInfo);
for(i=; i<G->vexnum; i++)
{
printf("输入第%d个顶点: ", i+);
memset(tmp, , sizeof(tmp));
scanf("%s", tmp);
G->vexs[i] = tmp[];
}
for(i=; i<G->vexnum; i++)
{
for(j=; j<G->vexnum; j++)
{
G->arcs[i][j].adj = INFINITY;
G->arcs[i][j].info = NULL;
}
}
for(k=; k<G->arcnum; k++)
{
printf("输入第%d条弧: 弧尾, 弧头,权值: ", k+);
memset(tmp, , sizeof(tmp));
scanf("%s", tmp);
sscanf(tmp, "%c,%c,%d", &v1, &v2, &w);
i = LocateVex(*G, v1);
j = LocateVex(*G, v2);
G->arcs[i][j].adj = w;
if(IncInfo){
//
}
G->arcs[j][i] = G->arcs[i][j];
}
return ;
} int CreateGraph(MGraph *G)
{
printf("输入图类型: -有向图(0), -有向网(1), -无向图(2), +无向网(3): ");
scanf("%d", &G->kind);
switch(G->kind)
{
case DG:
case DN:
case UDG:
printf("还不支持!\n");
return -;
case UDN:
return CreateUDN(G);
default:
return -;
}
} //////////////////////////////////////////////////////////////
// 打印邻接矩阵中的信息
//////////////////////////////////////////////////////////////
void printG(MGraph G)
{
printf("\n打印邻接矩阵:\n");
if(G.kind == DG){
printf("类型:有向图;顶点数 %d, 弧数 %d\n", G.vexnum, G.arcnum);
}else if(G.kind == DN){
printf("类型:有向网;顶点数 %d, 弧数 %d\n", G.vexnum, G.arcnum);
}else if(G.kind == UDG){
printf("类型:无向图;顶点数 %d, 弧数 %d\n", G.vexnum, G.arcnum);
}else if(G.kind == UDN){
printf("类型:无向网;顶点数 %d, 弧数 %d\n", G.vexnum, G.arcnum);
}
int i = , j = ;
printf("\t");
for(i=; i<G.vexnum; i++)
printf("%c\t", G.vexs[i]);
printf("\n");
for(i=; i<G.vexnum; i++){
printf("%c\t", G.vexs[i]);
for(j=; j<G.vexnum; j++){
if(G.arcs[i][j].adj == INFINITY){
printf("INF\t");
}else{
printf("%d\t", G.arcs[i][j].adj);
}
}
printf("\n");
}
} //---------求最小生成树的算法。(普里姆算法)----------------------------------------- //////////////////////////////////////////////////////////////
// 定义辅助数组CloseEdge: 记录从顶点集U到V-U的代价最小的边的辅助数组。
//////////////////////////////////////////////////////////////
struct Edge{
VertexType adjvex;
VRType lowcost;
}CloseEdge[MAX_VERTEX_NUM]; //////////////////////////////////////////////////////////////
// 返回辅助数组中, 权值最小的顶点的位置。
//////////////////////////////////////////////////////////////
int minimum(struct Edge edgelist[], int count)
{
int ret = -;
int min = INFINITY;
int i = ;
for(i=; i<count; i++) {
if ((edgelist[i].lowcost) && (edgelist[i].lowcost < min)) {
ret = i;
min = edgelist[i].lowcost;
}
}
return ret;
} //////////////////////////////////////////////////////////////
// 采用普里姆算法求最小生成树的算法。
//////////////////////////////////////////////////////////////
void MinSpanTree_PRIM(MGraph G, VertexType v)
{
printf("\n采用普里姆算法求邻接矩阵存储的带权的无向网的最小生成树,所求最小生成树的边依次为:\n");
int k = -;
int i = ;
int j = ;
k = LocateVex(G, v);
//辅助数组初始化
for(j=; j<G.vexnum; j++){
if(j!=k)
{
CloseEdge[j].adjvex = v;
CloseEdge[j].lowcost = G.arcs[k][j].adj;
}
}
//初始状态下, U={v}
CloseEdge[k].lowcost = ;
//选择其余的G.vexnum-1个顶点
for(i=; i<G.vexnum; i++)
{
//求出T的下一个结点,第k个顶点
k = minimum(CloseEdge, G.vexnum);
//输出生成树的边
printf("%c, %c\n", CloseEdge[k].adjvex, G.vexs[k]);
//第k个顶点并入U集合
CloseEdge[k].lowcost = ;
//新顶点并入U后重新选择最小边。
for(j=; j<G.vexnum; j++){
if(G.arcs[k][j].adj < CloseEdge[j].lowcost){
CloseEdge[j].adjvex = G.vexs[k];
CloseEdge[j].lowcost = G.arcs[k][j].adj;
}
}
}
} int main(int argc, char *argv[])
{
MGraph G;
if(CreateGraph(&G) > -)
printG(G);
MinSpanTree_PRIM(G, G.vexs[]);
return ;
}

最小生成树(普里姆算法)

代码运行

/home/lady/CLionProjects/untitled/cmake-build-debug/untitled
输入图类型: -有向图(0), -有向网(1), -无向图(2), +无向网(3): 3 创建一个无向网(带权):
输入顶点数,弧数,其他信息标志位: 6,10,0
输入第1个顶点: a
输入第2个顶点: b
输入第3个顶点: c
输入第4个顶点: d
输入第5个顶点: e
输入第6个顶点: f
输入第1条弧: 弧尾, 弧头,权值: c,a,1
输入第2条弧: 弧尾, 弧头,权值: c,b,5
输入第3条弧: 弧尾, 弧头,权值: c,d,5
输入第4条弧: 弧尾, 弧头,权值: c,e,6
输入第5条弧: 弧尾, 弧头,权值: c,f,4
输入第6条弧: 弧尾, 弧头,权值: a,b,6
输入第7条弧: 弧尾, 弧头,权值: b,e,3
输入第8条弧: 弧尾, 弧头,权值: e,f,6
输入第9条弧: 弧尾, 弧头,权值: f,d,2
输入第10条弧: 弧尾, 弧头,权值: d,a,5 打印邻接矩阵:
类型:无向网;顶点数 6, 弧数 10
a b c d e f
a INF 6 1 5 INF INF
b 6 INF 5 INF 3 INF
c 1 5 INF 5 6 4
d 5 INF 5 INF INF 2
e INF 3 6 INF INF 6
f INF INF 4 2 6 INF 采用普里姆算法求邻接矩阵存储的带权的无向网的最小生成树,所求最小生成树的边依次为:
a, c
c, f
f, d
c, b
b, e Process finished with exit code 0

图->连通性->最小生成树(普里姆算法)的更多相关文章

  1. 最小生成树---普里姆算法(Prim算法)和克鲁斯卡尔算法(Kruskal算法)

    普里姆算法(Prim算法) #include<bits/stdc++.h> using namespace std; #define MAXVEX 100 #define INF 6553 ...

  2. ACM第四站————最小生成树(普里姆算法)

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

  3. 图解最小生成树 - 普里姆(Prim)算法

    我们在图的定义中说过,带有权值的图就是网结构.一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边.所谓的最小成本,就是n个顶点,用n-1条边把一个连通图连接 ...

  4. 查找最小生成树:普里姆算法算法(Prim)算法

    一.算法介绍 普里姆算法(Prim's algorithm),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之 ...

  5. 普里姆算法(Prim)

    概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图(带权图)里搜索最小生成树.即此算法搜索到的边(Edge)子集所构成的树中,不但包括了连通图里的所有顶点(Vertex)且其所有边的权 ...

  6. HDU 1879 继续畅通工程 (Prim(普里姆算法)+Kruskal(克鲁斯卡尔))

    继续畅通工程 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Sub ...

  7. 最小生成树-普利姆算法lazy实现

    算法描述 lazy普利姆算法的步骤: 1.从源点s出发,遍历它的邻接表s.Adj,将所有邻接的边(crossing edges)加入优先队列Q: 2.从Q出队最轻边,将此边加入MST. 3.考察此边的 ...

  8. 最小生成树-普利姆算法eager实现

    算法描述 在普利姆算法的lazy实现中,参考:普利姆算法的lazy实现 我们现在来考虑这样一个问题: 我们将所有的边都加入了优先队列,但事实上,我们真的需要所有的边吗? 我们再回到普利姆算法的lazy ...

  9. Prim算法(普里姆算法)

    描述: 一个连通图的生成树是指一个极小连通子图,它含有图中的全部顶点,但只有足以构成一棵树的 n-1 条边.我们把构造连通网的最小代价生成树成为最小生成树.而Prim算法就是构造最小生成树的一种算法. ...

随机推荐

  1. 《转》Babel 入门教程

    ECMAScript 6是JavaScript语言的下一代标准,已经在2015年6月正式发布了.Mozilla公司将在这个标准的基础上,推出JavaScript 2.0.ES6的目标,是使得JavaS ...

  2. MyBatis源码分析-基础支持层反射模块Reflector/ReflectorFactory

    本文主要介绍MyBatis的反射模块是如何实现的. MyBatis 反射的核心类Reflector,下面我先说明它的构造函数和成员变量.具体方法下面详解. org.apache.ibatis.refl ...

  3. 【Java】移动JDK路径后,修改环境变量不生效 Error: could not open `C:\Program Files\Java\jre1.8.0_131\lib\amd64\jvm.cfg'

    场景: JDK原先装在C盘的,现在移动到了D盘,并在环境变量修改了%JAVA_HOME%的新路径,但是CMD中输入java后依然报错. Error: could not open `C:\Progra ...

  4. python3二元Logistics Regression 回归分析(LogisticRegression)

    纲要 boss说增加项目平台分析方法: T检验(独立样本T检验).线性回归.二元Logistics回归.因子分析.可靠性分析 根本不懂,一脸懵逼状态,分析部确实有人才,反正我是一脸懵 首先解释什么是二 ...

  5. MyCAT简易入门 (Linux)

    MyCAT是mysql中间件,前身是阿里大名鼎鼎的Cobar,Cobar在开源了一段时间后,不了了之.于是MyCAT扛起了这面大旗,在大数据时代,其重要性愈发彰显.这篇文章主要是MyCAT的入门部署. ...

  6. Python套接字编程(1)——socket模块与套接字编程

    在Python网络编程系列,我们主要学习以下内容: 1. socket模块与基本套接字编程 2. socket模块的其他网络编程功能 3. SocketServer模块与简单并发服务器 4. 异步编程 ...

  7. [Tensorflow] Cookbook - The Tensorflow Way

    本章介绍tf基础知识,主要包括cookbook的第一.二章节. 方针:先会用,后定制 Ref: TensorFlow 如何入门? Ref: 如何高效的学习 TensorFlow 代码? 顺便推荐该领域 ...

  8. iPhone 上你可能还不知道的小技巧

    用了这么久的 iPhone,这些技巧你可能都还不知道哦. 1.怎么用耳机切歌? 将耳机的话筒部位的中间(平时暂停用的,按一下)连按两下 即可. 连按两下,下一首. 连按三下,上一首. 2.摇一摇,相当 ...

  9. NIO相关概念之Channel

    通道(Channel)是java.nio的第二个主要创新.它们既不是一个扩展也不是一项增强,而是全新.极好的Java I/O示例,提供与I/O服务的直接连接.Channel用于在字节缓冲区和位于通道另 ...

  10. Sciter返回json

    sciter::value arr[200]; for (int i = 0; i < (int)m_fileList.size(); i++) { cv::Mat img = cv::imre ...