生成树——Kruscal & Prim

生成树——Kruscal & Prim

关于树

MST——最小生成树

  • 无向无环的联通图
  • 任意两个结点之间有且仅有一条简单路径的无向图
  • 任何边均为桥的连通图
  • 没有圈,且在任意不同两点见添加一条边之后所得图含唯一的一个圈的图

Kruscal

适合简单图

原理

不断加边

  • 认定每个点都是一个独立的集合
  • 按照边的权值检查每一条边
    • 如果该边连接两个不同的集合,将边加入MST,合并两个集合
  • 集合的判定和合并使用并查集

图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zGEma39r-1628932722088)(https://i.loli.net/2021/08/02/Vkvg8dHDKIZ4Xln.png)]

代码模板

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 2*1e6;
int n,m;//点,边
int ans=0;//权值和
int sum=0;//边数
int fat[maxn];//集合

struct node//结构体存边
{
    int u,v,w;
}edge[maxn];

bool cmp(const node &a,const node &b){//按权值sort
    return a.w<b.w;
}
/*递归
int find_set(int x){//查找是否在集合
    if(fat[x]!=x)
        return find_set(fat[x]);
    else
        return x;
}
*/
int find_set(int x){//查找是否在集合
    if(fat[x]!=x)
        fat[x] = find_set(fat[x]);
    return fat[x];
}

int kruscal(){
    sort(edge+1,edge+m+1,cmp);//按权值排序

    for(int i=1;i<=n;i++)
        fat[i] = i;//先认定每个人都是一个集合

	for(int i=1;i<=m;i++){
        //合并
        int x=find_set(edge[i].u);
        int y=find_set(edge[i].v);
        //如果两点在同一个集合中,说明已经连通,则不能选择
        if(x==y)
            continue;
        //不连通的话,合并集合
        fat[x]=y;
        sum++;//增加边数
        ans += edge[i].w;//增加边权
        if(sum == n-1)//n个点n-1条边
        {
            cout << ans << endl;
            return 0;
        }
	}
	cout<<"orz"<<endl;//图不连通
	return 0;
}

int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++)
        cin>>edge[i].u>>edge[i].v>>edge[i].w;
    kruscal();
	return 0;
}

Prim

适合稠密图

原理

不断加点

  • 选择任意点为根
  • 找不在树上的离树最近的点加入生成树

图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQY7LsfK-1628932722089)(https://i.loli.net/2021/08/02/IpfgBm1aH9XEYDO.png)]

代码模板

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 2*1e6,inf=1e9+7;
int n,m;//点,边
int cnt=0;
int sum=0;//边数
int ans=0;//权值和
int head[maxn];
int dis[maxn];//距离
bool vis[maxn];//标记

struct node
{
    int v,w,next;
}edge[maxn];
//链式前向星存边
void add_edge(int u,int v,int w){
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

/*
随机选择一个点 将它的dist变为0(我选择的是1号节点),并将它标记(因为最小生成树每个点只能用一次)

然后我们遍历与这个点相连的点 将它们的dist更新(更新为它们的边权值)

现在 我们得到了多个dist不为0x7f7ff的点了

于是 我们选择其中dist最小的点 标记它 然后再遍历与它相连的点,更新它们的dist

不断的重复 重复 直到所有点都被标记为止

当然 也存在orz的情况,这时 被标记的点的数量是小于n的,加个判断就好啦
*/

void prim(){
    for(int i=1;i<=n;i++)
        dis[i] = inf;//没到的距离均为inf
    dis[1] = 0;//起始点距离为0
    for(int j=1;j<=n;j++){
        int now=-1,minn=inf;
        for(int i=1;i<=n;i++){
            if(!vis[i] && minn>dis[i])
            {
                minn = dis[i];
                now = i;
            }
        }
        if(now==-1){//是否联通标记
            ans=-1;
            return;
        }
        vis[now]=1;
        ans += dis[now];
        for(int i=head[now];i!=-1;i=edge[i].next){
            int v = edge[i].v;
            if(!vis[v] && dis[v]>edge[i].w)
                dis[v]=edge[i].w;
        }
    }

}

int main(){
    cin>>n>>m;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        add_edge(u,v,w);
        add_edge(v,u,w);
    }
    prim();
    //cout<<sum<<endl;
    if(ans==-1)
        cout<<"orz"<<endl;
    else
        cout<<ans<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值