【图论】最小生成树

最小生成树

定义:如果带权图G(V,E)有子图G’(V’,E’)是一棵树且满足V=V’,则称G‘是G的一颗生成树,其中边权之和最小的称为最小生成树。

(带权图: 如果给图 G 的每一条边都赋一个值, 则图 G 称为带 权图, 无权图可以视为边权均为 1 的带权图)

(树: 如果图 G 中没有环, 则称 G 是一棵树)

n 个结点的树必定有 n-1 条边)

Kruskal算法

时间复杂度: O(ElogE)

算法

1、将所有边按照边权排序, 边集 S = ∅;

2、从小到大依次将边加入 S 中, 使得 S 中不出现环;

3 、|S| = n - 1 或所有边遍历完毕后, 算法停止.

使用并查集来检查 S 中是否有环:

​ 处于同一连通分量的结点只用一个代表元来表示;

​ 代表元不同的点属于不同的连通分量, 否则属于同一连通分量;

​ 在 S 中已经属于同一连通分量的点之间不加边, 即可保证无环.

并查集详解

模板

struct edge{
    int u,v,w;
}e[maxm];
void addedge(int u,int v,int w){
    e[tot].u=u;e[tot].v=v;e[tot++].w=w;//e[tot].w=w;tot++;
}
bool cmp(edge a,edge b){
    return a.w<b.w;
}
int find(int x){//并查集
    if(fa[x]==-1) return x;
    else return fa[x]==find(fa[x]);
}
int Kruskal(int n){
    memset(fa,-1,sizeof(fa));
    sort(e,e+tot,cmp);
    int cnt(0),ans(0);
    for(int i=0;i<tot;i++){
        int u=edge[i].u,v=edge[i].v,w=edge[i].w;
        int t1=find(u);t2=find(v);
        if(t1!=t2){
            ans+=w;fa[t1]=t2;cnt++;
        }
        if(cnt==n-1) break; //确定树
    }
    if(cnt<n-1) return -1;
    else return ans;//最小权
}

示例题目来源

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 <cstdlib>
#include <cstring>
#include <algorithm>
#define maxm 100
using namespace std;
int tot(0);
int fa[maxm];
struct edge{
    int u,v,w;
}e[maxm];
void addedge(int u,int v,int w){
    e[tot].u=u;e[tot].v=v;e[tot++].w=w;//e[tot].w=w;tot++;
}
bool cmp(edge a,edge b){
    return a.w<b.w;
}
int find(int x){//并查集
    if(fa[x]==-1) return x;
    else return fa[x]==find(fa[x]);
}
int Kruskal(int n){
    memset(fa,-1,sizeof(fa));
    sort(e,e+tot,cmp);
    int cnt(0),ans(0);
    for(int i=0;i<tot;i++){
        int u=e[i].u,v=e[i].v,w=e[i].w;
        int t1=find(u),t2=find(v);
        if(t1!=t2){
            ans+=w;fa[t1]=t2;cnt++;
        }
        if(cnt==n-1) break; //确定树
    }
    if(cnt<n-1) return -1;
    else return ans;//最小权
}
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
		{
			int u,v,w;
			cin>>u>>v>>w;
			addedge(u,v,w);
		}
	cout<<Kruskal(n);
	return 0;	
} 
Prim 算法

时间复杂度: O(V ^2)

算法

1、设有点集 S = {v0} ⊂ V, 边集 E’= ∅,V0∈V;

2、 取 V \ S 中距离 S 最近的点 v , 加入 S 中, 并将该最短边加入 E’中;

3、 当 |S| = n 时, 算法停止;

4、 若 E ’ 的权为 , 则原图不连通, 否则E‘为最小生成树.

点到点集的距离:在这里插入图片描述

模板

int lowc[maxn];//各点到点集的最短距离
int cost[maxn][maxn];
int Prim(int n){
    int ans(0);
    memset(vis,false,sizeof(vis));//点集S标记
    vis[0]=true;
    for(int i=1;i<n;i++) lowc[i]=cost[0][i];//耗费矩阵
    for(int i=1;i<n;i++){
        int minc=inf,p=-1;
    	for(int j=0;j<n;j++)
            if(!vis[j]&& minc >lowc[j]){
                minc=lowc[j];p=j;
            }
        if(minc==inf) return -1;//权为∞,不连通
        ans+=minc;vis[p]=true;
        for(int j=0;j<n;j++)
            if(!vis[j]&& lowc[j]>cost[p][j])
                lowc[j]=cost[p][j];
    }       
    return ans;
}

示例

题目同上,代码如下

#include <iostream>
#include <algorithm>
#include <cstring>
#define maxn 100
#define inf 0x3f3f3f
using namespace std;
int lowc[maxn];
int cost[maxn][maxn]; 
bool vis[maxn];
int Prim(int n)
{
	int ans(0);
	memset(vis,false,sizeof(vis));
	vis[0]=true;
	for(int i=1;i<n;i++) lowc[i]=cost[0][i];
	for(int i=1;i<n;i++){
		int minc=inf,p=-1;
		for(int j=0;j<n;j++)
			if(!vis[j]&&minc>lowc[j])
				{minc=lowc[j];p=j;} 
		if(minc==inf) return -1;
		ans+=minc;vis[p]=true;
		for(int j=0;j<n;j++)
			if(!vis[j]&&lowc[j]>cost[p][j])
				lowc[j]=cost[p][j];
	}
	return ans;
}
int main()
{
	int n,m;
	cin>>n>>m;
	memset(cost,inf,sizeof(cost));
	for(int i=0;i<m;i++)
		{
			int u,v,w;
			cin>>u>>v>>w;
			cost[u-1][v-1]=w;
			cost[v-1][u-1]=w;
		}
	cout<<Prim(n);
	return 0;	
} 

Prim算法很棒的解释
参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值