求最小生成树【图论】【笔记】

一、定义

用大白话来解释就是,用一个图把所有的点都连接起来,而且所有边的总权值在所有图中最小。

 

二、算法

(1)克鲁斯卡尔算法

 

基本思想:

第一步:把所有的边从小到大排序。

第二步:开始选边,每一个边要保证都不相连,即不能成圈。

第三步:判断一下,是不是选了n-1条边,因为当不成全的边数为n-1的时候,定可以把一张图连起来。

 

大白话说:

先排序再判圈

 

难点:判断如何成圈?需要用到并查集中的根节点,如果两个点相连可以构成圈的话,那它们两个的根节点相同

拿这张图中的点为例,如果v1与v4相连会成圈,v4的根节点与v1的根节点绝对相同(都是v4或者v1)。

所以,不会成圈的前提就是两个点的根节点不同即可。

(2)以例题为例,详情看注释

HUD1863畅通工程

省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。经过调查评估,得到的统计表中列出了有可能建设公路的若干条道路的成本。现请你编写程序,计算出全省畅通需要的最低成本。

Input

测试输入包含若干测试用例。每个测试用例的第1行给出评估的道路条数 N、村庄数目M ( < 100 );随后的 N
行对应村庄间道路的成本,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间道路的成本(也是正整数)。为简单起见,村庄从1到M编号。当N为0时,全部输入结束,相应的结果不要输出。

Output

对每个测试用例,在1行里输出全省畅通需要的最低成本。若统计数据不足以保证畅通,则输出“?”。

Sample Input

3 3
1 2 1
1 3 2
2 3 4
1 3
2 3 2
0 100

Sample Output

3
?
#include <stdio.h>
#include <vector>
#include <algorithm>
#include <sstream>
#include <iostream>
#include <cstring>
typedef unsigned long long ll;
const int INF=1e9;
const int maxn=1e6+5;
using namespace std;
int dis[maxn];
int pre[maxn];
struct node{
    int x,y,len;
}edge[maxn];
int Find(int x)//寻找根节点
{
    return x==pre[x]?x:Find(pre[x]);
}
bool Merge(int x,int y)
{
    int x1=Find(x);
    int y1=Find(y);
    if(x1!=y1)判断根节点是否相同
    {
        pre[x1]=y1;//不相同之后将他们连接起来
        return true;
    }
    return false;
}
bool cmp(node a,node b)
{
    return a.len<b.len;
}
void restart()
{
   for(int i=0;i<maxn;i++)
        pre[i]=i;//并查集初始化
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)&&n)
    {
        restart();
        for(int i=1;i<=n;i++)
            scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].len);
        sort(edge+1,edge+1+n,cmp);//先排序
        int ans=0;
        int paid=0;
        for(int i=1;i<=n&&ans<=m-1;i++)//挨个遍历
        {
            if(Merge(edge[i].x,edge[i].y))//再判圈
            {
                ans++;
                paid+=edge[i].len;
            }
        }
        if(ans<m-1) printf("?\n");
        else printf("%d\n",paid);
    }
    return 0;
}

 

(2)prim算法

 

基本思想:

第一步:指定一个起点开始,一般从1开始。

第二步:用擂台法比较出最小的那个边,并起点移动到那个点。

第三步:重复上面前两步。

 

大白话表述:与迪杰斯特拉的求最短路的算法类似,只不过将判断条件改变了。

 

成立条件:有一个点没有联通到【dis[i]=INF】即没有形成最小生成树

 

还是以上面题为例,详情看代码:

#include <stdio.h>
#include <vector>
#include <algorithm>
#include <sstream>
#include <iostream>
#include <cstring>
typedef unsigned long long ll;
const int INF=1e9;
const int maxn=1e6+5;
using namespace std;
int dis[maxn];
int pre[maxn];
struct node{
    int x,y,len;
}edge[maxn];
int head[maxn];
int vis[maxn];
ll cnt=0;
ll paid=0;
void restart()
{
    memset(head,-1,sizeof(head));
    cnt=0;paid=0;
}
void addedge(int u,int v,int w)
{
    edge[cnt].y=v;
    edge[cnt].len=w;
    edge[cnt].x=head[u];
    head[u]=cnt++;
}
void prim(int n)//与dijkstra的算法类似。
{
    for(int i=1;i<=n;i++) dis[i]=(i==1?0:INF),vis[i]=0;
    while(2)
    {
        int k=-1,len=INF;
        for(int i=1;i<=n;i++)//选择
        {
            if(!vis[i]&&len>dis[i])
            {
                k=i;
                len=dis[i];
            }
        }
        vis[k]=1;
        if(k==-1) break;
        for(int i=head[k];i!=-1;i=edge[i].x)//更新
        {
            int e=edge[i].y;
            if(dis[e]>edge[i].len)
            {
                dis[e]=edge[i].len;
                paid+=edge[i].len;
            }
        }
    }
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)&&n)
    {
        restart();
        for(int i=1;i<=n;i++)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            addedge(x,y,z);
        }
        prim(m);
        int flag=0;
        for(int i=1;i<=m;i++)//判断是否生成了最小生成树
            if(dis[i]==INF)
            {
                flag=1;
                break;
            }
        if(flag) puts("?");
        else printf("%d\n",paid);
    }
    return 0;
}

 

三、总结

1.这是求最小生成树最长用的两种算法,但不排除也会有变式。

2.克鲁斯卡尔算法,是以边为基准,而prim是以点为基准。

3.有意见可以交流,一起加油!

 

 

 

 

 

 

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务