算法竞赛备赛——【图论】最小生成树

发布于:2025-07-24 ⋅ 阅读:(17) ⋅ 点赞:(0)

最小生成树

无向图 连通图才有最小生成树 可以判断图是否连通

记住思路 不要死记模板 灵活建图写代码

Prim算法

由Prim提出。该算法的基本思想是从一个结点开始,不断加点(而不是 Kruskal 算法的加边)。每次总是选出一个离生成树距离最小的点去加入生成树,最后实现最小生成树。类似Dijkstra算法。时间复杂度 O(V^2)------->稠密图

模板

#include<bits/stdc++.h>
using namespace std;
const int maxn=5005;
const int inf=0x7fffffff;
int cnt;
struct edge
{
    int u,w,next;
}e[2*maxn];
int head[maxn];
int dis[maxn];
bool vis[maxn];
int n,m;
void add(int x,int y,int w)		//链式前向星的加点方法
{
    cnt++;
    e[cnt].u=y;
    e[cnt].w=w;
    e[cnt].next=head[x];
    head[x]=cnt;
}
int prim()
{
    for(int i=1;i<=n;i++)dis[i]=inf;
    dis[1]=0;
    vis[1]=1;
    int now=1;
    for(int i=head[now];i;i=e[i].next)	//链式前向星的遍历方法   可利用堆优化
    {
        int u=e[i].u;
        dis[u]=min(dis[u],e[i].w);
    }
    int tot=0;
    int sum=0;
    while(tot<n-1)
    {
        int mindis=inf;
        for(int i=1;i<=n;i++)
        {
            if(!vis[i]&&dis[i]<mindis)
            {
                now=i;
                mindis=dis[i];
            }

        }
        if(mindis==inf)return -1;//图不连通
            tot++;
            sum+=mindis;
            vis[now]=1;
        for(int i=head[now];i;i=e[i].next)	//链式前=前向星的遍历方法
        {
            int u=e[i].u;
            if(vis[u])continue;
            dis[u]=min(dis[u],e[i].w);
        }
    }
    return sum;
}
int main()
{

    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    int ans=prim();
    if(ans==-1)printf("orz");
    else printf("%d",ans);
}


例题

P1265 公路修建 - 洛谷

只能用Prim算法 边有5000*5000

完全图不用存图

设置变量不要用y1,y2,y3…

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define INF 1e9
int n;
struct node{
	int x,y;
}p[5005];
double dis[5005];
int v[5005];
double cal(const node& p1,const node& p2){
	return (double)sqrt((double)(p1.x-p2.x)*(p1.x-p2.x)+(double)(p1.y-p2.y)*(p1.y-p2.y));//用double 
}

int main(){
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>p[i].x>>p[i].y; 
	}
	dis[1]=0;
	v[1]=1;
	for(int i=2;i<=n;i++){
		dis[i]=cal(p[1],p[i]);
	}
	double minn=INF,ans=0;
	int k;
	for(int i=1;i<n;++i){
		minn=INF;
		k=-1;
		for(int j=1;j<=n;++j){
			if(v[j]==0&&dis[j]<minn){
				k=j;
				minn=dis[j];
			}
		}
		v[k]=1;
		ans+=minn;
		for(int j=1;j<=n;++j){
			if(v[j]==0&&dis[j]>cal(p[k],p[j])){
				dis[j]=cal(p[k],p[j]);
			}
		}
	} 
	printf("%.2lf\n",ans);
	return 0;
} 

Kruskal算法

Kruskal 算法:由 Kruskal 发明。该算法的基本思想是从小到大加入边,是个贪心算法。在加入边时,需要通过 并查集 来判断该边的两个端点是否属于同一集合(树),避免形成 “环”。时间复杂度 O(ElogE)-----> 稀疏图

模板

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include <climits>
#include<utility>
using namespace std;
const int maxn = 110; // 最大顶点数
const int maxm = 10010; // 最大边数
struct Edge {// 使用结构体储存每一条边,便于排序
    int u, v, w; // 表示有一条 (u,v) 的无向边,边权为 w
} e[maxm+5];
int ecnt;// 用于边表计数
void addEdge(int u, int v, int w){ // 加入一条无向边
    ++ecnt;
    e[ecnt].u = u;
    e[ecnt].v = v;
    e[ecnt].w = w;
}
int fa[maxn]; // 并查集相关
int find(int x) {
    return x == fa[x] ? x : fa[x] = find(fa[x]); // 路径压缩
}
int n,m; // 顶点数 边数 
bool cmp(const Edge &a, const Edge &b){
    return a.w < b.w;
}
int Kruskal() { // Kruskal 算法核心过程
    for (int i = 1; i <= n; i++) {
        fa[i] = i; // 初始化并查集
    }
    sort (e + 1, e + ecnt + 1, cmp);
    int sum = 0;
    for (int i = 1; i <= ecnt; i++) {
        int u = e[i].u;
        int v = e[i].v;
        u = find(u);
        v = find(v);
        if (u != v) {
            fa[u] = v;
            sum += e[i].w;
        }
    }
    return sum;
}
int main(){
    scanf("%d %d",&n,&m);
    int x,y,w;
    for (int i = 1; i <= m; i++) {
            scanf("%d %d %d", &x,&y,&w);
            addEdge(x, y, w);
        }
    
    int ans = Kruskal();
    printf("%d\n",ans);
    return 0;
}

例题

P1194 买礼物 - 洛谷

引入虚假的点来转化点权

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
int a,b; 
struct Edge{
	int u,v,w;
}e[300000];
int fa[505];
int cnt;

bool cmp(const Edge& x,const Edge& y){
	return x.w<y.w;
}

int find(int x){
	if(x!=fa[x]) return fa[x]=find(fa[x]);
	return fa[x];
}

int main(){
	cin>>a>>b;
	//引入虚假的物品0,买了0再买其他物品
	for(int i=1;i<=b;++i){
		e[++cnt].u=0;
		e[cnt].v=i;
		e[cnt].w=a;
	}
	int k;
	for(int i=1;i<=b;++i){
		for(int j=1;j<=b;++j){
			cin>>k;
			if(k!=0){
				e[++cnt].u=i;
				e[cnt].v=j;
				e[cnt].w=k;
			}
		}
	}
	sort(e+1,e+cnt+1,cmp);
	for(int i=0;i<=b;++i){
		fa[i]=i;
	}
	int x,y,fx,fy,ans=0;
	for(int i=1;i<=cnt;++i){
		x=e[i].u;
		y=e[i].v;
		fx=find(x);
		fy=find(y);
		if(fx!=fy){
			ans+=e[i].w;
			fa[fx]=fy;
		}
	}
	cout<<ans<<endl;
	return 0;
} 

网站公告

今日签到

点亮在社区的每一天
去签到