最小生成树问题

  1. 最小生成树问题(Mininum Spanning Tree MST): 在给定无向图中,确定一棵树T,满足三个条件:a.包含图的所有顶点;b.边都是图的边;c.整棵树的边权之和最小

  2. MST的性质: 包含n-1个结点;连通;树不唯一(最小边权和唯一)

Prim算法:点贪心

基本思想:类Dijstra

与Dijstr思想类似,只不过d[]的含义不同

d[v]:v与已被标记的顶点构成的集合s的最短距离。

毕竟,要求的是最小生成树,而集合s中的顶点又都在树中,所以需要关注顶点v与集合s的最短距离而不是与源点s(根节点)的最短距离。

而用中介点(此时是结合s中的所有点而不是到源点s距离最小的点)优化其他点的思想就是Dijstra算法的内核,所以只需要改变Dijstra算法中d[]数组的含义,即可顺利实现Prim算法。

如对Dijstra算法不太熟悉,可参看:<数据结构>图的最短路径问题

伪代码

建议与Dijstra算法的伪代码进行比较阅读

Prim(G, d[]){
初始化;
for(循环n次){
u = 使d[u]最小的还未被访问的顶点的标号;
记u已被访问;
for(从u出发能到达的所有顶点v){
if(v未被访问&&以u为中介点使得v与**集合s**的最短距离d[v]更优){ //唯一与Dijstra算法不同之处
将G[u][v]赋值给v与集合s的最短距离d[v];//将G[u][v]赋给d[v] 而不再是 d[u] + G[u][v]
}
}
}
}

代码实现

增加int ans记录边权和。

#include<stdio.h>
#include<algorithm>
using namespace std; #define MAXV 100
#define INF 100000000
int n, G[MAXV][MAXV]; //邻接矩阵实现图G
int d[MAXV]; //顶点与集合s的最短距离
bool vis[MAXV] = {false}; int Prim(){
fill(d, d+MAXV*MAXV, INF);
d[0] = 0; //只有0号顶点与s距离为0,其余为INF
int ans = 0; //存放边权之和
for(int i = 0; i < n; i++){ //寻找 到集合s距离最短&&未被标记的顶点
int u = -1, MIN = INF;
for(int j = 0; j < n; j++){
if(vis[j] == false && d[j] < MIN){
u = j;
MIN = d[j];
}
} if(u == -1) return ; //图不全连通,无法构建最小生成树
vis[u] = true;
ans += d[u]; //边权增加
for(int v = 0; v < n; v++){
//如果v未访问 && u能到达v && 以u为中介点可以使d[v]更优
if(vis[v] == false && G[u][v] != INF && G[u][v] < d[v]){
d[v] = G[u][v];
}
}
} return ans; //返回最小边权之和
}

复杂度分析:O(VlogV + E)

同Dijstra算法。

kruskal算法:边贪心

基本思想: 充分利用MST性质

遵循下述三个步骤:

  1. 对所有边按边权从小到大排序
  2. 按边权测试所有边,如果当前测试边所连接的两个顶点不在同一个连通块,则把这条测试边加入最小生成树中;否则将边舍弃。
  3. 执行步骤2,直到树中的边数 == 顶点数-1。

伪代码

int kruskal(){
令最小生成树的边权之和为ans,最小生成树的当前边数为Num_Edge;
将所有边按边权从小到大排序;
for(从小到大枚举所有边){
if(当前测试边的两个端点在不同的连通块中){
将该测试边加入最小生成树;
ans += 测试边边权;
最小生成树的当前边数Num_Edge++;
当边数Num_Edge == 顶点数-1 时结束循环;
}
}
return ans;
}

关键要解决两个问题

  1. 将所有边按边权从小到大排序————>sort函数(自行定义cmp)
  2. 当前测试边的两个端点在不同的连通块中————>并查集(顺带可以完成“将该测试边加入最小生成树;”)

代码实现

#include<stdio.h>
#include<algorithm>
using namespace std;
const int MAXV = 110;
const int MAXE = 10010; //定义边集合
struct edge{
int u, v;
int cost;
}E[MAXE];
bool cmp(edge a, edge b){ //按边权从小到大
return a.cost < b.cost;
}
//并查集部分
int father[MAXV];
int findFather(int x){
int a = x;
while(x != father[x])
x = father[x]; //路径压缩:让x的子结点直接指向x,减少中间路径
while(a != father[a]){
int z = a;
a = father[a];
father[a] = x;
} return x;
} //kruskal部分,返回最小生成树的边权之和,参数n为顶点个数,m为图的边数
int kruskal(int n, int m){
//ans为所求边权之和,Num_Edge为当前生成树的边数
int ans = 0, Num_Edge = 0;
for(int i = 0; i < n; i++){ //顶点范围是0-(n-1)
father[i] = i; //并查集初始化
}
sort(E, E+m, cmp); //所有边按边权从小到达排序
for(int i = 0; i < m; i++){ //枚举所有边
int faU = findFather(E[i].u); //查询两个端点所在的集合的根节点
int faV = findFather(E[i].v);
if(faU != faV){ //如果不在一个集合中
father[faU] = faV;//合并集合(即吧测试边加入最小生成树中)
ans += E[i].cost;//边权和增加
Num_Edge ++;//当前生成树的边数+1
if(Num_Edge == n-1) break; //边数 == n-1时结束算法
}
} if(Num_Edge != n-1) return -1; //不连通时返回-1
else return ans;
} int main(){
int n,m;
scanf("%d%d",&n,&m); //顶点数、边数
for(int i = 0; i < m; i++){
scanf("%d%d%d", &E[i].u,&E[i].v,&E[i].cost); //两端点编号, 边权
}
int ans = kruskal(n,m); //算法入口
printf("%d\n", ans);
return 0;
}

复杂度分析:O(ElogE)

  • 主要来源:sort()函数[O(ElogE)];本质是快速排序
  • 次要来源:一重for循环[O(E)]

算法选择

一般情况下

  • Prim: 稠密图
  • kruskal: 稀疏图

<数据结构>图的最小生成树的更多相关文章

  1. [从今天开始修炼数据结构]图的最小生成树 —— 最清楚易懂的Prim算法和kruskal算法讲解和实现

    接上文,研究了一下算法之后,发现大话数据结构的代码风格更适合与前文中邻接矩阵的定义相关联,所以硬着头皮把大话中的最小生成树用自己的话整理了一下,希望大家能够看懂. 一.最小生成树 1,问题 最小生成树 ...

  2. hdu 1233:还是畅通工程(数据结构,图,最小生成树,普里姆(Prim)算法)

    还是畅通工程 Time Limit : 4000/2000ms (Java/Other)   Memory Limit : 65536/32768K (Java/Other) Total Submis ...

  3. 数据结构与算法--最小生成树之Kruskal算法

    数据结构与算法--最小生成树之Kruskal算法 上一节介绍了Prim算法,接着来看Kruskal算法. 我们知道Prim算法是从某个顶点开始,从现有树周围的所有邻边中选出权值最小的那条加入到MST中 ...

  4. 数据结构与算法--最小生成树之Prim算法

    数据结构与算法--最小生成树之Prim算法 加权图是一种为每条边关联一个权值或称为成本的图模型.所谓生成树,是某图的一棵含有全部n个顶点的无环连通子图,它有n - 1条边.最小生成树(MST)是加权图 ...

  5. 无向带权图的最小生成树算法——Prim及Kruskal算法思路

    边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权. 最小生成树(MST):权值最小的生成树. 生成树和最小生成树的应用:要连通n个城市需要n-1条边线路.可以 ...

  6. 图的最小生成树(java实现)

    1.图的最小生成树(贪心算法) 我两个算法的输出都是数组表示的,当前的索引值和当前索引对应的数据就是通路,比如parent[2] = 5;即2和5之间有一个通路,第二个可能比较好理解,第一个有点混乱 ...

  7. C++编程练习(10)----“图的最小生成树“(Prim算法、Kruskal算法)

    1.Prim 算法 以某顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树. 2.Kruskal 算法 直接寻找最小权值的边来构建最小生成树. 比较: Kruskal 算法主要是针对边来展开,边数 ...

  8. "《算法导论》之‘图’":最小生成树(无向图)

    本文主要参考自<算法>. 加权图是一种为每条边关联一个权值或是成本的图模型.这种图能够自然地表示许多应用.在一幅航空图中,边表示航线,权值则可以表示距离或是费用.在一幅电路图中,边表示导线 ...

  9. 数据结构--图 的JAVA实现(上)

    1,摘要: 本系列文章主要学习如何使用JAVA语言以邻接表的方式实现了数据结构---图(Graph),这是第一篇文章,学习如何用JAVA来表示图的顶点.从数据的表示方法来说,有二种表示图的方式:一种是 ...

随机推荐

  1. 大数据学习day34---spark14------1 redis的事务(pipeline)测试 ,2. 利用redis的pipeline实现数据统计的exactlyonce ,3 SparkStreaming中数据写入Hbase实现ExactlyOnce, 4.Spark StandAlone的执行模式,5 spark on yarn

    1 redis的事务(pipeline)测试 Redis本身对数据进行操作,单条命令是原子性的,但事务不保证原子性,且没有回滚.事务中任何命令执行失败,其余的命令仍会被执行,将Redis的多个操作放到 ...

  2. 大数据学习day23-----spark06--------1. Spark执行流程(知识补充:RDD的依赖关系)2. Repartition和coalesce算子的区别 3.触发多次actions时,速度不一样 4. RDD的深入理解(错误例子,RDD数据是如何获取的)5 购物的相关计算

    1. Spark执行流程 知识补充:RDD的依赖关系 RDD的依赖关系分为两类:窄依赖(Narrow Dependency)和宽依赖(Shuffle Dependency) (1)窄依赖 窄依赖指的是 ...

  3. Windows zip版本安装MySQL

    Windows --MySQL zip版本安装记录: step1. 官网download zip包:http://cdn.mysql.com//Downloads/MySQL-5.7/mysql-5. ...

  4. MyBatis中sql实现时间查询的方法

    <if test="startTime != null and startTime !=''"> AND lTime >= #{startTime} </i ...

  5. UILabel总结

    UILabel 能显示文字,不能直接通过addTarget...方法监听点击 1. 常见属性 @property(nonatomic,copy) NSString *text; 显示文字 @prope ...

  6. 【spring AOP】@Pointcut的12种用法

    @Pointcut用来标注在方法上来定义切入点. 使用格式:@ 注解(value="表达标签 (表达式格式)").如:@Pointcut("execution(* com ...

  7. Java分层思想

    从最常规的分层结构来说,系统层次从上到下依次为: 表现层/UI层/界面层:主要是客户端的展示. 服务层/业务层:直接为客户端提供的服务或功能.也是系统所能对外提供的功能. 领域层:系统内的领域活动. ...

  8. Mysql资料 慢查询

    目录 一.简介 二.查询 三.开启 永久配置 临时配置 四.测试 一.简介 MySQL的慢查询,全名是慢查询日志,是MySQL提供的一种日志记录,用来记录在MySQL中响应时间超过阀值的语句. 具体环 ...

  9. Python pyecharts绘制柱状图

    本文摘抄至https://05x-docs.pyecharts.org/#/zh-cn/charts_base?id=bar%ef%bc%88%e6%9f%b1%e7%8a%b6%e5%9b%be%e ...

  10. 转:builder模式分析

    建造者模式 11.1 变化是永恒的 又是一个周三,快要下班了,老大突然拉住我,喜滋滋地告诉我:"牛叉公司很满意我们做的模型,又签订了一个合同,把奔驰.宝马的车辆模型都交给我们公司制 作了,不 ...