图论——最小生成树

最小生成树

  • 给定一个无向图,如果它的某个子图中任意两个顶点都互相连通并且是一棵树,那么这棵树就叫做生成树。如果边上有权值,那么使得边权和最小的生成树叫做最小生成树。

prim算法 O ( E ∗ l o g ( V ) ) ) O(E*log(V))) O(Elog(V)))

  • 首先从某个顶点出发,贪心的选择和当前顶点相连权值的最小的顶点,把该顶点加入集合中去。直到所有顶点都加入集合为止
模板:
const int MAX_V=1999;
const int INF = 99999;
int cost[MAX_V][MAX_V];//cost[i][j]表示顶点i和j之间的边的权值
int mincost[MAX_V];//mincost[i]表示从集合X出发的边到顶点i的最小权值(X是生成树顶点的集合)
bool used[MAX_V];
int V;//V表示顶点数量

int prim() {
    for(int i = 0; i < V; i++) {
  	  	mincost[i] = INF;
  	  	used[i] = false;
	}
  	mincost[0] = 0;
  	int res = 0;
  	while(true) {
    	int v = -1;
    	for(int u = 0; u < V; u++) {//找到需要加入当前X集合外的权值最小的顶点v
      		if(!used[u] && (v == -1 || mincost[u] < mincost[v]))
        	v = u;
    	}
    if(v == -1)
    	break;
    used[v] = true;
    res += mincost[v];//把权值加入进去
    for(int u = 0; u < V; u++)//更新,顶点v到所有其他可连接的顶点的距离
      	mincost[u] = min(mincost[u], cost[v][u]);
  	}
  	return res;
}

例题:洛谷 P 1194 P1194 P1194
问题描述:
又到了一年一度的明明生日了,明明想要买 B B B样东西,巧的是,这 B B B样东西价格都是 A A A元。
但是,商店老板说最近有促销活动,也就是:如果你买了第 I I I样东西,再买第 J J J样,那么就可以只花 K I , J K_{I,J} KI,J元,更巧的是, K I , J K_{I,J} KI,J竟然等于 K J , I K_{J,I} KJ,I​ 。
现在明明想知道,他最少要花多少钱。

输入
第一行两个整数, A , B A,B A,B
接下来 B B B行,每行 B B B个数,第 I I I行第 J J J个为 K I , J K_{I,J} KI,J
我们保证 K I , J = K J , I K_{I,J}=K_{J,I} KI,J=KJ,I并且 K I , I = 0 K_{I,I}=0 KI,I=0
特别的,如果 K I , J = 0 K_{I,J}=0 KI,J=0,那么表示这两样东西之间不会导致优惠。

输出
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz

样例输入

3 3
0 2 4
2 0 2
4 2 0

样例输出

7
#include <cstdio>
#include <algorithm>
#define INF 0x3f3f3f3f
#define MAXN_V 505
using namespace std;
int cost[MAXN_V][MAXN_V];
int mincost[MAXN_V];
bool used[MAXN_V];
int V, a;
int prim() {
	for(int i = 1; i <= V; i++) {
		mincost[i] = INF;
		used[i] = false;
	}
	mincost[1] = 0;
	int res = 0;
	while(1) {
		int v = -1;
		for(int i = 1; i <= V; i++) {
			if(!used[i] && (v == -1 || mincost[i] < mincost[v]))
				v = i;
		}
		if(v == -1) break;
		used[v] = 1;
		if(mincost[v] < a) //如果当前加入的权值比初始价格便宜才加,否则加a
			res += mincost[v];
		else
			res += a;
		for(int i = 1; i <= V; i++) 
			mincost[i] = min(mincost[i], cost[v][i]);
	}
	return res;
}
int main() {
	scanf("%d %d", &a, &V);
	for(int i = 1; i <= V; i++)
		for(int j = 1; j <= V; j++) {
			scanf("%d", &cost[i][j]);
			if(i == j) cost[i][j] = INF;
			else if(cost[i][j] == 0) cost[i][j] = a;
		}
	int ans = prim();
	printf("%d\n", ans+a);
	return 0;
}

kruskal算法 ( O ( E ∗ l o g ( V ) ) ) (O(E*log(V))) (O(Elog(V)))

  • 按照边的权值的顺序从小到大查看一遍,如果不产生圈(通过并查集来判断是否会产生连通分量,若有则会产生圈),就把当前这个边加入到生成树中
模板:
const int MAX_E = 1000;
struct edge {
	int u, v, cost;
};
bool cmp(const edge &a, const edge &b) {
  return a.cost < b.cost;
}
edge es[MAX_E];
int V, E;
int kruskal() {
	sort(es, es+E, cmp);
	init_union_find(V);//初始化并查集
	int res = 0;
	for(int i = 0; i < E; i++) { 
		edge e = es[i];
    	if(!same(e.u,e.v)) { //如果当前要加入的边不在集合中,则可加进来 
      		unite(e.v,e.u);
      		res += cost;
    	}
  	}
  	return res;
}

例题:洛谷 P 3366 P3366 P3366
问题描述:
给出一个无向图,求出最小生成树,如果该图不连通,则输出orz

输入
第一行包含两个整数 N 、 M N、M NM,表示该图共有 N N N个结点和 M M M条无向边。
( N &lt; = 5000 , M &lt; = 200000 ) (N&lt;=5000,M&lt;=200000) N<=5000M<=200000
接下来 M M M行每行包含三个整数 X i 、 Y i 、 Z i X_i、Y_i、Z_i XiYiZi,表示有一条长度为 Z i Z_i Zi的无向边连接结点 X i 、 Y i X_i、Y_i XiYi

输出
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz

样例输入

4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3

样例输出

7
#include <cstdio>
#include <algorithm>
#define MAXN 200000 + 100
using namespace std;
struct Edge {
	int u, v, cost;
	Edge(){}
	Edge(int _u, int _v, int _cost):u(_u), v(_v), cost(_cost){}
}e[MAXN];

bool cmp(Edge a, Edge b) {
	return a.cost < b.cost;
}
int V, E, n, cnt, p[MAXN], k; 

int find(int x) { //判断是否是在同一个集合中
	if(x != p[x])
		return p[x] = find(p[x]);
	else
		return x;
} 

int kruskal() {
	for(int i = 0; i <= k; i++)  // 初始化并查集 
		p[i] = i;
	sort(e+1, e+1+k, cmp);
	int res = 0;
	for(int i = 1; i <= k; i++) {
		Edge t = e[i];
		int x = find(t.u);
		int y = find(t.v);
		if(x != y) {	//如果不在一个集合中,就把这条边加进这个集合然后加上权值
			p[x] = y;
			cnt++;
			res += t.cost;
		}
	}
	return res;
}

int main() {
	scanf("%d%d", &V, &E);
	for(int i = 1; i <= E; i++) {
		k++;
		scanf("%d%d%d", &e[k].u, &e[k].v, &e[k].cost);
	}
	int ans = kruskal();
	if(cnt != V-1) printf("orz\n");
 	else printf("%d", ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值