Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【算法】关于图论中的最小生成树(Minimum Spanning Tree)详解

【算法】关于图论中的最小生成树(Minimum Spanning Tree)详解

原创
作者头像
短短的路走走停停
修改于 2019-05-14 02:06:36
修改于 2019-05-14 02:06:36
8.2K0
举报
文章被收录于专栏:程序猿声程序猿声

本节纲要

  • 什么是图(network)
  • 什么是最小生成树 (minimum spanning tree)
  • 最小生成树的算法

什么是图(network)?

这里的图当然不是我们日常说的图片或者地图。通常情况下,我们把图看成是一种由“顶点”和“边”组成的抽象网络。在各个“顶点“间可以由”边“连接起来,使两个顶点间相互关联起来。图的结构可以描述多种复杂的数据对象,应用较为广泛,看下图:

为了更好地说明问题,下面我们看一个比较老套的通信问题:

在各大城市中建设通信网络,如下图所示,每个圆圈代表一座城市,而边上的数字代表了建立通信连接的价格。那么,请问怎样才能以最小的价格使各大城市能直接或者间接地连接起来呢?

我们需要注意两点:

  • 最小的价格
  • 各大城市可以是直接或者间接相连的

稍稍留心可以发现,题目的要求是,城市只需要直接或者间接相连,因此,为了节省成本,我们稍稍优化一下上述方案如下:

可以看到,我们砍掉了原先在AD,BE之间的两条道路,建设价格自然就降下来了。当然这个方案也是符合我们题目的要求的。按照国际惯例,这里要说蛋是了。上面的实例由于数据很简单,优化的方案很easy就看出来了。但在实际中,数据量往往是非常庞大的。所以,我们更倾向于设计一种方法,然后利用计算机强大的运算能力帮我们处理这些数据得出最优的方案。

那么,针对上述问题,我们一起来看看如何应用图的相关知识来实现吧。

什么是最小生成树(minimum spanning tree)

为了直观,还是用图片给大家解释一下:

  • 对于一个图而言,它可以生成很多树,如右侧图2,图3就是由图1生成的。
  • 从上面可以看出生成树是将原图的全部顶点以最少的边连通的子图,对于有n个顶点的连通图,生成树有n-1条边,若边数小于此数就不可能将各顶点连通,如果边的数量多于n-1条边,必定会产生回路。
  • 对于一个带权连通图,生成树不同,树中各边上权值总和也不同,权值总和最小的生成树则称为图的最小生成树。

关于最小生成树的算法(Prim算法和Kruskal算法)

Prim算法

基本思想:

假设有一个无向带权图G=(V,E),它的最小生成树为MinTree=(V,T),其中V为顶点集合,T为边的集合。求边的集合T的步骤如下:

①令 U={u0},T={}。其中U为最小生成树的顶点集合,开始时U中只含有顶点u0(u0可以为集合V中任意一项),在开始构造最小生成树时我们从u0出发。

②对所有u∈U,v∈(V – U)(其中u,v表示顶点)的边(u,v)中,找一条权值最小的边(u’,v’),将这条边加入到集合T中,将顶点v’加入集合U中。

③直到将V中所有顶点加入U中,则算法结束,否则一直重复以上两步。

④符号说明:我们用大写字母表示集合,用小写字母表示顶点元素,用<>表示两点之间的边。

为了更好的说明问题,我们下面一步一步来为大家展示这个过程。

  1. 初始状态:U={a} V={b,c,d,e } T={}
  2. 集合U和V相关联的权值最小的边是<a,b>,于是我们将b加入U。U={a,b},V={d,c,e },T={<a,b>}
  3. 此时集合U和V相关联的权值最小的边是<b,c>,于是我们将c加入U。U={a,b,c} ,V={d,e },T={<a,b>, <b,c>}
  4. 显然此时集合U和V中相关联的权值最小的边是<c,d>,于是我们将d加入U。U={a,b,c,d} ,V={e },T={<a,b>, <b,c>,<c,d>}
  5. 最后集合U和V中相关联的权值最小的边是<d,e>,于是将e加入U。U={a,b,c,d,e} ,V={},T={<a,b>, <b,c>,<c,d>,<d,e>}。
    到此所有点访问完毕。

代码实现

代码语言:txt
AI代码解释
复制
//prime算法
//将城市X标记为visit=true时,就表示该城市加入到集合U,用sum累加记录边的总费用

#include<iostream>
#define NO 99999999   //99999999代表两点之间不可达
#define N 5
using namespace std;

bool visit[N];
long long money[N] = { 0 };
long long graph[N][N] = {0};

void initgraph()
{
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			scanf(" %lld", &graph[i][j]);
		}
	}
	
}

void printgraph()
{
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			printf(" %lld", graph[i][j]);
		}
	}

}

int prim(int city)
{
	initgraph();
	printgraph();
	int index = city;
	int sum = 0;
	int i = 0;
	int j = 0;
	cout <<"访问节点:" <<index << "\n";
	memset(visit, false, sizeof(visit));
	visit[city] = true;
	for (i = 0; i < N; i++)
	{
		money[i] = graph[city][i];//初始化,每个与城市city间相连的费用存入money,以便后续比较
	}
		
	for (i = 1; i < N; i++)
	{
		int minor = NO;
		for (j = 0; j < N; j++)
		{
			if ((visit[j] == false) && money[j] < minor)  //找到未访问的城市中,与当前最小生成树中的城市间费用最小的城市
			{
				minor = money[j];
				index = j;
			}
		}
		visit[index] = true;
		cout << "访问节点:" << index << "\n";
		sum += minor; //求总的最低费用
		/*这里是一个更新,如果未访问城市与当前城市间的费用更低,就更新money,保存更低的费用*/
		for (j = 0; j < N; j++)
		{
			if ((visit[j] == false) && money[j]>graph[index][j])
			{
				money[j] = graph[index][j];
			}
		}
	}
	cout << endl;
	return sum;               //返回总费用最小值
}
int main()
{
	cout << "修路最低总费用为:"<< prim(0) << endl;//从城市0开始
	return 0;
}

Kruskal算法

解最小生成树的另一种常见的算法是Kruskal算法,它比Prim算法更直观。

Kruskal算法的做法是:每次都从剩余边中选取权值最小的,当然,这条边不能使已有的边产生回路。手动求解会发现Kruskal算法异常简单,下面是一个例子

先对边的权值排个序:

1(V0,V4)、2(V2,V6)、4(V1,V3)、6(V1,V2)、8(V3,V6)、10(V5,V6)、12(V3,V5)、15(V4,V5)、20(V0,V1)

首选边1(V0,V4)、2(V2,V6)、4(V1,V3)、6(V1,V2),此时的图是这样

显然,若选取边8(V3,V6)则会出现环,则必须抛弃8(V3,V6),选择下一条10(V5,V6)没有问题,此时图变成这样

显然,12(V3,V5)同样不可取,选取15(V4,V5),边数已达到要求,算法结束。最终的图是这样的

算法逻辑很容易理解,但用代码判断当前边是否会引起环的出现则很棘手。这里简单提一提连通分量

  • 在无向图中,如果从顶点vi到顶点vj有路径,则称vi和vj连通。如果图中任意两个顶点之间都连通,则称该图为连通图,否则,将其中较大的连通子图称为连通分量。
  • 在有向图中,如果对于每一对顶点vi和vj,从vi到vj和从vj到vi都有路径,则称该图为强连通图;否则,将其中的极大连通子图称为强连通分量。

算法说明

为了判断环的出现,我们换个角度来理解Kruskal算法的做法:初始时,把图中的n个顶点看成是独立的n个连通分量,从树的角度看,也是n个根节点。我们选边的标准是这样的:若边上的两个顶点从属于两个不同的连通分量,则此边可取,否则考察下一条权值最小的边。

于是问题又来了,如何判断两个顶点是否属于同一个连通分量呢?这个可以参照并查集的做法解决。它的思路是:如果两个顶点的根节点是一样的,则显然是属于同一个连通分量。这也同样暗示着:在加入新边时,要更新父节点。

代码语言:txt
AI代码解释
复制
//kruskal算法

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<map>
#include<set>
#include<list>
#include<vector>
using namespace std;
#define N 10005
#define M 50005
#define qm 100005
#define INF 2147483647
struct arr{
	int ff, tt, ww;
}c[M << 1];// 存储边的集合,ff,tt,ww为一条从ff连接到tt的权值为ww的边 
int tot = 0;//边的总数 
int ans = 0;//最小生成树的权值和 
int f[N];//并查集 
bool comp(const arr & a, const arr & b){
	return a.ww < b.ww;
}
int m, n;//边数量,点数量 
int getfa(int x){
	return f[x] == x ? x : f[x] = getfa(f[x]);
}//并查集,带路径压缩 
 
inline void add(int x, int y, int z){
	c[++tot].ff = x;
	c[tot].tt = y;
	c[tot].ww = z;
	return;
}//新增一条边 

void kruscal(){
	for (int i = 1; i <= n; i ++) f[i] = i;
	for (int i = 1; i <= m; i ++){
		int fx = getfa(c[i].ff);//寻找祖先 
		int fy = getfa(c[i].tt);
		if (fx != fy){//不在一个集合,合并加入一条边 
			f[fx] = fy;
			ans += c[i].ww;
		}
	}

	return;
} 
int main(){
	freopen("input10.txt", "r", stdin);
	freopen("output10.txt", "w", stdout);
	scanf("%d%d",&n, &m);
	int x, y, z;
	for (int i = 1; i <= m; i ++){
		scanf("%d %d %d", &x, &y, &z);
		add(x, y, z);
	}
	sort(c + 1, c + 1 + m, comp);//快速排序 
	kruscal();
	printf("%d\n", ans);
	return 0;
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
基础算法 | 关于图论中最小生成树(Minimum Spanning Tree)那些不可告人的秘密
最近双11又快到了 有女朋友的忙着帮女朋友清空购物车 有男朋友的忙着叫男朋友帮清购物车 而小编就比较牛逼了 小编沉迷学习,已经无法自拔。 那么今天小编又给大家带来什么好玩的东西呢? 没错 那就是小编通过 夜夜修仙,日日操劳 终于修成的正果 用起来很牛逼,说出去很装逼的 最小生成树 纲要 - 什么是图(network) - 什么是最小生成树 (minimum spanning tree) - 最小生成树的算法 1 什么是图 这里的图当然不是我们日常说的图片或者地图。通常情况下,我们把图看成是一种由“顶点(no
用户1621951
2018/04/19
1K0
基础算法 | 关于图论中最小生成树(Minimum Spanning Tree)那些不可告人的秘密
每周学点大数据 | No.17最小生成树
No.17期 最小生成树(一) Mr. 王:我们再来讲一个时间亚线性算法——最小生成树问题。这里先简单介绍一下树的概念。 小可:那什么是树呢? Mr. 王:树的简单定义,就是一个没有回路的连通无向图。
灯塔大数据
2018/04/08
9750
每周学点大数据 | No.17最小生成树
算法:图解最小生成树之克鲁斯卡尔(Kruskal)算法
我们在前面讲过的《克里姆算法》是以某个顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树的。同样的思路,我们也可以直接就以边为目标去构建,因为权值为边上,直接找最小权值的边来构建生成树也是很自然的
s1mba
2018/01/12
3K0
算法:图解最小生成树之克鲁斯卡尔(Kruskal)算法
加权无向图----Prim算法实现最小生成树
上一篇:加权无向图的实现 加权无向图----Kruskal算法实现最小生成树 图的生成树是它的一棵含有其所有顶点的无环连通子图,加权图的最小生成树(MST)是它的一棵权值最小的生成树。 切分:图的一种切分是将图的所有顶点分为两个非空且不重合的两个集合。横切边是一条连接两个属于不同集合的顶点的边。 切分定理:在一幅加权图中,给定任意的切分,它横切边中权重最小者必然属于图的最小生成树。 切分定理是解决最小生成树问题的所有算法的基础。  Prim算法能够得到任意加权连通无向图的最小生成树。 数据结构设计: 采用一
SuperHeroes
2018/05/30
1.7K0
最小生成树算法实现与分析:Prim 算法,Kruskal 算法;
连通图:无向图G中,若从顶点i到顶点j有路径相连,则称i,j是连通的;如果G是有向图,那么连接i和j的路径中所有的边都必须同向;如果图中任意两点之间都是连通的,那么图被称作连通图。
西湖醋鱼
2020/12/30
1.5K0
最小生成树算法实现与分析:Prim 算法,Kruskal 算法;
图论-最小生成树kruskal算法(Java)
和prim算法以顶点为出发点不同,kruskal算法以边为中心,将所有边以小到大排序,遍历边,如果当前边的两个顶点有一个没有访问过,则记录该边,直到记录的边到达顶点数-1时,即所有顶点都可以相连,为最小生成树 实现代码:
aruba
2021/12/06
8780
图论-最小生成树kruskal算法(Java)
最小生成树总结
给定一张带权无向图 G=(V,E),n = |V|, m = |E|。由 V 中全部 n 个顶点和 E 中 n-1 条边构成的无向连通子图被称为 G 的一棵生成树。边权和最小的生成树被称为无向图 G 的最小生成树(Minimum Spanning Tree,MST)。
Here_SDUT
2022/06/29
1.2K0
最小生成树-Prim算法和Kruskal算法
Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算
用户1215536
2018/02/05
3.9K0
最小生成树-Prim算法和Kruskal算法
复杂网络(2)--图论的基本理论-最小生成树问题
该文章是一篇技术文章,主要介绍了如何通过编辑距离算法实现文本相似度的计算。文章首先介绍了编辑距离算法的原理,然后详细讲解了基于编辑距离的文本相似度计算方法,并给出了具体的实现代码。最后,文章还探讨了编辑距离算法在技术社区中的应用,包括相似度计算和相似问答系统。
锦小年
2018/01/02
1.6K0
复杂网络(2)--图论的基本理论-最小生成树问题
图的应用——最小生成树
最小生成树 生成树(极小连通子图):含有图中全部n个顶点,但只有n-1条边。并且n-1条边不能构成回路。 [在这里插入图片描述] 生成森林:非连通图每个连通分量的生成树一起组成非连通图的生成森林。 [在这里插入图片描述] 求最小生成树 使用不同的遍历图的方法,可以得到不同的生成树 从不同的顶点出发,也可能得到不同的生成树。 按照生成树的定义,n 个顶点的连通网络的生成树有 n 个顶点、n-1 条边。在网的多个生成树中,寻找一个各边权值之和最小的生成树 构造最小生成树的准则 必须只使用该网中的边来构造最小生成
ruochen
2021/06/29
8820
图的应用——最小生成树
数据结构 第17讲 沟通无限校园网——最小生成树(kruskal算法)
构造最小生成树还有一种算法,Kruskal算法:设G=(V,E)是无向连通带权图,V={1,2,…,n};设最小生成树T=(V,TE),该树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),Kruskal算法将这n个顶点看成是n个孤立的连通分支。它首先将所有的边按权值从小到大排序,然后只要T中选中的边数不到n−1,就做如下的贪心选择:在边集E中选取权值最小的边(i,j),如果将边(i,j)加入集合TE中不产生回路(圈),则将边(i,j)加入边集TE中,即用边(i,j)将这两个连通分支合并连接成一个连通分支;否则继续选择下一条最短边。把边(i,j)从集合E中删去。继续上面的贪心选择,直到T中所有顶点都在同一个连通分支上为止。此时,选取到的n−1条边恰好构成G的一棵最小生成树T。
rainchxy
2018/09/13
1.4K0
图详解第三篇:最小生成树(Kruskal算法+Prim算法)
连通图中的每一棵生成树,都是原图的一个极大无环子图,即:从其中删去任何一条边,生成树就不在连通;反之,在其中引入任何一条新边,都会形成一条回路。
YIN_尹
2024/01/23
4K0
图详解第三篇:最小生成树(Kruskal算法+Prim算法)
图的应用(最小生成树,拓扑排序)
应用图解决现实问题是我们使用图这种数据结构的原因所在。 最小生成树是图的应用中很常见的一个概念,一个图的最小生成树不是唯一的,但最小生成树的边的权值之和纵使唯一的。最小生成树的算法主要有Prim算法和Kruskal算法。这两种算法都是基于贪心算法策略(只考虑眼前的最佳利益,而不考虑整体的效率)。 拓扑排序是指由一个有向无环图的顶点组成的序列,此序列满足以下条件:
跋扈洋
2022/12/03
4670
PHP数据结构(十一) ——图的连通性问题与最小生成树算法(2)
PHP数据结构(十一)——图的连通性问题与最小生成树算法(2) (原创内容,转载请注明来源,谢谢) 再次遇到微信公众号限制字数3000字的问题。因此将Kruskal算法放于本文中进行描述。本文接上一篇文章。 4、Kruskal算法 1)该算法的时间复杂度为O(eloge),e表示边的数目,即该算法的时间复杂度和顶点数目无关。该算法适用于边数较少的稀疏网。 2)算法内容 假设N={V, {E}}是连通网,算法初始状态为包含图中的所有的点,没有边的T=(V, {
用户1327360
2018/03/07
1.3K0
PHP数据结构(十一) ——图的连通性问题与最小生成树算法(2)
最小生成树学习
生成树:给定无向图G=(V,E),连接G中所有点,且边集是E的n-1条边构成的无向连通子图称为G的生成树(Spanning Tree),而边权值总和最小的生成树称为最小生成树(Minimal Spanning Tree,MST)。
fishhh
2022/10/31
5900
最小生成树的两种方法(Kruskal算法和Prim算法)
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
233333
2020/01/09
2.1K0
最小生成树Kruskal算法模板题C++实现
基本思想:(1)构造一个只含n个顶点,边集为空的子图。若将图中各个顶点看成一棵树的根节点,则它是一个含有n棵树的森林。(2)从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图。也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之(3)依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。
里克贝斯
2021/05/21
8860
最小生成树Kruskal算法模板题C++实现
图的应用:最小生成树
在学习了图的基本结构和遍历方式后,我们再继续地深入学习一些图的基本应用。在之前的数据结构中,我们并没接触太多的应用场景,但是图的这两类应用确是面试或考试中经常出现的问题,而且出现的频率还非常高,不得不来好好说一说。
硬核项目经理
2021/06/10
8060
图的应用:最小生成树
5.4.1 最小生成树(Minimum-Spanning-Tree,MST)
一个连通的生成树是图中的极小连通子图,它包括图中的所有顶点,并且只含尽可能少的边。这意味着对于生成树来说,若砍去它的一条边,就会使生成树变成非连通图;若给它添加一条边,就会形成图中的一条回路。
week
2018/08/24
1.3K0
数据结构与算法——最小生成树
在之前的文章中已经详细介绍了图的一些基础操作。而在实际生活中的许多问题都是通过转化为图的这类数据结构来求解的,这就涉及到了许多图的算法研究。
五分钟学算法
2019/05/06
1.7K0
数据结构与算法——最小生成树
推荐阅读
相关推荐
基础算法 | 关于图论中最小生成树(Minimum Spanning Tree)那些不可告人的秘密
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档