图论 - 无向图最小生成树

N个点M条边的无向连通图,每条边有一个权值,求该图的最小生成树。

Input

第1行:2个数N,M中间用空格分隔,N为点的数量,M为边的数量。(2 <= N <= 1000, 1 <= M <= 50000)
第2 - M + 1行:每行3个数S E W,分别表示M条边的2个顶点及权值。(1 <= S, E <= N,1 <= W <= 10000)

Output

输出最小生成树的所有边的权值之和。

Input示例

9 14
1 2 4
2 3 8
3 4 7
4 5 9
5 6 10
6 7 2
7 8 1
8 9 7
2 8 11
3 9 2
7 9 6
3 6 4
4 6 14
1 8 8

Output示例

37

---------------------------------------------------------------我是分割线^_^-----------------------------------------------------------
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXN = 1005;
const int MAXM = 50005;

int cost[MAXN][MAXN];
struct Edge
{
	int s, t, d; // s为起点,t为终点,d为权值
} edge[MAXM];

/**
* prim算法
* @param cost: 赋权邻接矩阵
* @param n: 节点的编号,为1-n
*/
int prim(int cost[][MAXN], int n)
{
	int rst = 0;
	bool vis[MAXN]; // 标记节点是否被加入
	int label[MAXN]; // 选择节点的依据
	memset(vis, false, sizeof(vis));

	vis[1] = true; // 从任意一个节点开始
	for (int i = 1; i <= n; ++i) {
		label[i] = cost[1][i];
	}
	for (int times = 1; times <= n - 1; ++times) { // 循环n - 1次,每次加入一个节点
		int minEdge = INF;
		int choosed = -1;
		for (int i = 1; i <= n; ++i) { //选择可扩张边上的节点
			if (!vis[i] && label[i] < minEdge) {
				minEdge = label[i];
				choosed = i;
			}
		}
		if (minEdge == INF) { // mincost没更新,说明原图没有联通
			return -1;
		}
		rst += minEdge;
		vis[choosed] = true;
		for (int i = 1; i <= n; ++i) { // 更新节点的标记值
			if (!vis[i] && cost[choosed][i] < label[i]) {
				label[i] = cost[choosed][i];
			}
		}
	}

	return rst;
}

void addEdge(int id, int s, int t, int d)
{
	edge[id].s = s;
	edge[id].t = t;
	edge[id].d = d;
}

bool cmp(Edge edge1, Edge edge2)
{
	return edge1.d < edge2.d;
}

int unionFind(int pre[], int x) {
	int r = x;
	while (pre[r] != r) {
		r = pre[r];
	}
	return r;
}

/**
* Kruskal算法
* @param
*/
int kruskal(Edge edge[], int n, int m)
{
	int rst = 0;
	int pre[MAXN]; // 并查集
	for (int i = 1; i <= n; ++i) {
		pre[i] = i;
	}

	sort(edge, edge + m, cmp); // 对所有边按权值从小到大排序

	int cnt = 0; // 对加入的边计数
	for (int i = 0; i < m; ++i) {
		int r1 = unionFind(pre, edge[i].s);
		int r2 = unionFind(pre, edge[i].t);
		if (r1 != r2) { //不构成环, 就加入生成树
			pre[r1] = r2;
			rst += edge[i].d;
			cnt++;
		}
		if (cnt >= n - 1) { // 已经加入n - 1条边,可以结束了
			break;
		}
	}
	if (cnt < n - 1) { // 加入的边小于n - 1,说明原图不联通
		return -1;
	}
	return rst;
}

int main()
{
	int n, m;
	int s, t, d;

	scanf("%d%d", &n, &m);
	if (m >= n * (n - 1) / 4) { // ?????Kruskal
		for (int i = 0; i < m; ++i) {
			scanf("%d%d%d", &s, &t, &d);
			addEdge(i, s, t, d);
		}
		cout << kruskal(edge, n, m) << endl;
	} else { // ?????Prim
		memset(cost, INF, sizeof(cost));
		for (int i = 0; i < m; ++i) {
			scanf("%d%d%d", &s, &t, &d);
			cost[s][t] = min(cost[s][t], d);
			cost[t][s] = cost[s][t];
		}
		cout << prim(cost, n) << endl;
	}

	return 0;
}

给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树.

一个连通图的生成树是一个极小连通子图,它含有图中全部顶点,但只有足以构成一棵树的n-1条边。


求最小生成树的算法
(1) 克鲁斯卡尔算法
图的存贮结构采用边集数组,且权值相等的边在数组中排列次序可以是任意的.该方法对于边相对比较多的不是很实用,浪费时间.
(2) 普里姆算法
图的存贮结构采用邻接矩阵.此方法是按各个顶点连通的步骤进行,需要用一个顶点集合,开始为空集,以后将以连通的顶点陆续加入到集合中,全部顶点加入集合后就得到所需的最小生成树 .




下面来具体讲下:
克鲁斯卡尔算法
方法:将图中边按其权值由小到大的次序顺序选取,若选边后不形成回路,则保留作为一条边,若形成回路则除去.依次选够(n-1)条边,即得最小生成树.(n为顶点数)

第一步:由边集数组选第一条边

第二步:选第二条边,即权值为2的边

第三步:选第三条边,即权值为3的边

第四步:选第四条边,即权值为4的边

第五步:选第五条边





普里姆算法
方法:从指定顶点开始将它加入集合中,然后将集合内的顶点与集合外的顶点所构成的所有边中选取权值最小的一条边作为生成树的边,并将集合外的那个顶点加入到集合中,表示该顶点已连通.再用集合内的顶点与集合外的顶点构成的边中找最小的边,并将相应的顶点加入集合中,如此下去直到全部顶点都加入到集合中,即得最小生成树.
例在下图中从1点出发求出此图的最小生成树,并按生成树的边的顺序将顶点与权值填入表中.

———————>先写出其邻接矩阵

第一步:从①开始,①进集合,用与集合外所有顶点能构成的边中找最小权值的一条边
①——②权6
①——③权1 -> 取①——③边
①——④权5



第二步:③进集合,①,③与②,④,⑤,⑥构成的最小边为
①——④权5
③——⑥权4 -> 取③——⑥边


第三步:⑥进集合,①,③,⑥与②,④,⑤构成的各最小边
①——②权6
③——②权5
⑥——④权2 -> 取⑥——④边


第四步:④进集合,①,③,⑥,④与②,⑤构成的各最小边
①——②权6
③——②权5 -> 取③——②边
⑥——⑤权6


第四步:②进集合,①,③,⑥,②,④与⑤构成的各最小边
②——⑤权3 -> 取②——⑤边




这也是在网上找到的一个Kruskal和Prim构造过程图,贴出来:



 
时间: 2024-10-08 10:04:19

图论 - 无向图最小生成树的相关文章

图论(五)------最小生成树

一个无向图G的最小生成树就是由该图的那些连接了G的所有顶点的边构成的树,且其总权重最低.最小生成树存在当且仅当G是连通的. 对于任何一生成树T,如果将一条不属于T的边e加进来,则产生一个圈.如果从圈中除去任意一条边,则又恢复树的特性.如果边e的权值比除去的边的值低,那么新生成的树的值就比原生成的树的值低.如果在建立树T的过程中每次添加的边在所有避免成圈的边中值最小,那么最后得到的生成树的值不能再改进.对于最小生成树的贪婪算法是成立的. 算法策略 在每一个步骤中都形成最小生成树的一条边.算法维护一

1212 无向图最小生成树

1212 无向图最小生成树 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 N个点M条边的无向连通图,每条边有一个权值,求该图的最小生成树. Input 第1行:2个数N,M中间用空格分隔,N为点的数量,M为边的数量.(2 <= N <= 1000, 1 <= M <= 50000) 第2 - M + 1行:每行3个数S E W,分别表示M条边的2个顶点及权值.(1 <= S, E <= N,1 <= W <= 10

51Nod-1212 无向图最小生成树

51Nod: 1212 无向图最小生成树. link: http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1212 1212 无向图最小生成树 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 N个点M条边的无向连通图,每条边有一个权值,求该图的最小生成树. Input 第1行:2个数N,M中间用空格分隔,N为点的数量,M为边的数量.(2 <= N <= 1000, 1 <= M <

所有边权均不相同的无向图最小生成树是唯一的证明

设G是所有边权均不相同的无向联通图. 证明一: 首先,易证图G中权值最小的边一定是最小生成树中的边.(否则最小生成树加上权值最小的边后构成一个环,去掉环中任意一条非此边则形成了另一个权值更小的生成树). 之后用反证法,假设G存在俩个不同的最小生成树 ①.设G的俩个不同的最小生成树T1 T2,设这俩颗生成树的并集为子图G1,G1为连通图且T1 T2显然为G1的最小生成树,由首先可得知俩颗生成树至少包含一条公共边,将G1中两颗生成树的公共边删去,得到子图G2.G2由一个或多个连通分量组成,其中至少有

51nod1212无向图最小生成树

1212 无向图最小生成树 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题  收藏  关注 N个点M条边的无向连通图,每条边有一个权值,求该图的最小生成树. Input 第1行:2个数N,M中间用空格分隔,N为点的数量,M为边的数量.(2 <= N <= 1000, 1 <= M <= 50000) 第2 - M + 1行:每行3个数S E W,分别表示M条边的2个顶点及权值.(1 <= S, E <= N,1 <= W <= 

无向图最小生成树Kruskal算法

问题 最小生成树的Kruskal算法 描述:有A.B.C.D四个点,每两个点之间的距离(无方向)是(第一个数字是两点之间距离,后面两个字母代表两个点):(1,'A','B'),(5,'A','C'),(3,'A','D'),(4,'B','C'),(2,'B','D'),(1,'C','D') 生成边长和最小的树,也就是找出一种连接方法,将各点连接起来,并且各点之间的距离和最小. 思路说明: Kruskal算法是经典的无向图最小生成树解决方法.此处列举两种python的实现方法.这两种方法均参考

无向图最小生成树Prim算法

问题 无向图最小生成树的Prim算法.一般的实现过程,采用了常规排序.本文在用Python实现中,使用了python的堆排序模块,不仅精简代码,而且提高效率. 思路说明 假设点A,B,C,D,E,F,两点之间有连线的,以及它们的距离分别是:(A-B:7);(A-D:5);(B-C:8);(B-D:9);(B-E:7);(C-E:5);(D-E:15);(D-F:6);(E-F:8);(E-G:9);(F-G:11) 关于Prim算法的计算过程,参与维基百科的词条:[普里姆算法] 将上述点与点关系

图论之最小生成树

prim算法 基本思想:先从无向图中标记若干顶点,再从标记顶点中选择权值最小的边,且这条边的另一顶点未被标记,直到所有的点都已被标记. 算法实现:用数组vis表示点的标记情况,数组low表示与点关联的最小权值情况. 1 int prim(int n) 2 { 3 int i,j; 4 int min,pos; 5 int ans=0; 6 7 vis[0]=1; pos=0; 8 9 for(i=0; i<n; i++) 10 if(i!=pos) low[i]=dis[pos][i]; 11

51nod1212 无向图最小生成树(Prim)

题目描述: N个点M条边的有向连通图,每条边有一个权值,求该图的最小生成树. Input 第1行:2个数N,M中间用空格分隔,N为点的数量,M为边的数量.(2 <= N <= 1000, 1 <= M <= 50000) 第2 - M + 1行:每行3个数S E W,分别表示M条边的2个顶点及权值.(1 <= S, E <= N,1 <= W <= 10000) OutPut 输出最小生成树的所有边的权值之和. Input示例 9 14 1 2 4 2 3