刷题笔记:最小生成树,欧拉回路,以及树的判断

一、最小生成树

算法

最小生成树(Minimum Spanning Tree,MST)是在一个给定的无向图G(V,E)中求一棵树T,使得这棵树拥有图G中的所有顶点,且所有边都是来自图G中的边,并且满足整棵树的边权之和最小。
求解最小生成树一般有两种算法,即prim算法与kruskal算法。这两个算法都是采用了贪心法的思想,只是贪心的策略不太一样。

prim算法

其基本思想是对图G(V,E)设置集合S,存放已被访问的顶点,然后每次从集合V-S中选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化所有从u能到达的顶点v与集合S之间的最短距离。这样的操作执行n次(n为顶点个数),直到集合S已包含所有顶点。可以发现,prim 算法的思想与最短路径中Djkstra算法的思想几乎完全相同,只是在涉及最短距离时使用了集合S代替Dijkstra算法中的起点s。

代码如下。

//最小生成树-prim算法
#include<iostream>
#define N 105
#define INF 0x3fffffff
using namespace std;
int g[N][N],dis[N];
bool vis[N];
void init(int n)
{
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            g[i][j]=INF;
        }
        g[i][i]=0;
        vis[i]=false;
        dis[i]=INF;
    }
}
int prim(int s,int n)
{
    int ans=0;
    dis[s]=0;
    for(int i=1;i<=n;i++){
        int u=-1,MIN=INF;
        for(int j=1;j<=n;j++){
            if(vis[j]==false&&dis[j]<MIN){
                MIN=dis[j];
                u=j;
            }
        }
        if(u==-1) return -1;
        vis[u]=true;
        ans+=dis[u];
        for(int v=1;v<=n;v++){
            if(vis[v]==false&&g[u][v]!=INF&&g[u][v]<dis[v]){
                dis[v]=g[u][v];
            }
        }
    }
    return ans;
}
int main()
{
    int m,n;
    while(cin>>m&&m!=0){
        cin>>n;
        init(n);
        while(m--){
            int a,b,c;
            cin>>a>>b>>c;
            if(g[a][b]>c) g[a][b]=g[b][a]=c;
        }
        int ans=prim(1,n);
        if(ans==-1) cout<<"?\n";
        else cout<<ans<<"\n";
    }
    return 0;
}

kruskal算法

从图中不断的选择路径最小的边进行归并,知道包含所有的节点。
其基本思想为:在初始状态时隐去图中的所有边,这样图中每个顶点都自成一个连通块。之后执行下面的步骤:

  1. 对所有边按边权从小到大进行排序。
  2. 按边权从小到大测试所有边,如果当前测试边所连接的两个顶点不在同一个连通块中,则把这条测试边加入当前最小生成树中;否则,将边舍弃。
  3. 执行步骤2,直到最小生成树中的边数等于总顶点数减1或是测试完所有边时结束。而当结束时如果最小生成树的边数小于总顶点数减1,说明该图不连通。

代码如下。

//最小生成树-kruskal算法
#include<iostream>
#include<string.h>
#include<algorithm>
#define M 105
#define INF 0x3fffffff
using namespace std;
int father[M];
bool isin[M][M];
struct  edge{
    int u,v;//边的两个断点
    int cost=0;//花费
}Edge[M];
bool cmp(edge e1,edge e2)
{
    return e1.cost<e2.cost;
}
int find(int x)
{
    if(x==father[x]) return x;
    else{
        return find(father[x]);
    }
}
int Kruskal(int n,int cnt)//n为顶点个数,cnt为边的个数
{
    int ans=0,edge_cnt=0;
    for(int i=1;i<=n;i++) father[i]=i;
    sort(Edge,Edge+cnt,cmp);//从小到大排序
    for(int i=0;i<cnt;i++){
        int fu=find(Edge[i].u);
        int fv=find(Edge[i].v);
        if(fu!=fv){
            father[fv]=fu;
            ans+=Edge[i].cost;
            edge_cnt++;
            if(edge_cnt==n-1) break;
        }
    }
    if(edge_cnt!=n-1) return -1;
    else return ans;
}
int main()
{
    int m,n;
    while(cin>>m&&m!=0){
        cin>>n;
        for(int i=0;i<M;i++)
            for(int j=0;j<M;j++)
                isin[i][j]=false;
        int cnt=0;
        while(m--){
            int a,b,c;
            cin>>a>>b>>c;
            if(isin[a][b]==false){
                isin[a][b]=isin[b][a]=true;
                Edge[cnt].u=a;
                Edge[cnt].v=b;
                Edge[cnt].cost=c;
                cnt++;
            }else{
                for(int j=0;j<cnt;j++){
                    if(((Edge[j].u==a&&Edge[j].v==b)||(Edge[j].u==b&&Edge[j].v==a))&&Edge[j].cost>c){
                        Edge[j].cost=c;
                        break;
                    }
                }
            }
        }
        int ans=Kruskal(n,cnt);
        if(ans==-1) cout<<"?\n";
        else cout<<ans<<"\n";
    }
    return 0;
}

刷题(牛客网-考研复试机试题

  1. 畅通工程
    直接使用prim算法/kruskal算法模板即可。

  2. 继续畅通工程
    同样可以直接使用算法模板,需要注意的是,已修建好的道路将其费用置为0。

  3. Freckles
    直接使用算法模板;需要计算每个节点间的距离;同时需要注意数据的类型。

二、欧拉回路

欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条回路。(不需要考虑孤立的节点)

  • 无向图存在欧拉回路:连通,所有节点均为偶度点
  • 有向图存在欧拉回路:连通,节点的入度之和=节点的出度之和
  • 题目:欧拉回路
    代码如下。
#include<iostream>
#define N 1005
using namespace std;
bool g[N][N];
int fa[N],d[N];
void init(int n)
{
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            g[i][j]=false;
        }
        fa[i]=i;
        g[i][i]=true;
        d[i]=0;
    }
}
int find(int x)
{
    if(x==fa[x]) return x;
    else return find(fa[x]);
}
bool solute(int n)
{
    //判断是否是连通,使用并查集
    int cnt=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(g[i][j]){
                int f1=find(i);
                int f2=find(j);
                if(f1!=f2){
                    fa[f1]=f2;
                }
            }
        }
    }
    for(int i=1;i<=n;i++)
        if(fa[i]==i&&d[i]) //不考虑孤立的点
           cnt++;
    if(cnt!=1) return false;
    //判断所有节点是否为偶度节点
    for(int i=1;i<=n;i++){
        if(d[i]%2) return false;
    }
    return true;
}
int main()
{
    int n,m;
    while(cin>>n&&n!=0){
        cin>>m;
        init(n);
        while(m-->0){
            int a,b;
            cin>>a>>b;
            g[a][b]=g[b][a]=true;
            if(a!=b){
                d[a]++;
                d[b]++;
            }
        }
        bool f=solute(n);
        if(f) cout<<"1\n";
        else cout<<"0\n";
    }
    return 0;
}

三、树的判断

什么是树?在无环的情况下,根节点的入度为0,其余节点的入度为1。

题目:Is It a Tree?

这道题的关键在于如何判断是否存在环,可以使用并查集进行判断。

代码如下。

#include<iostream>
#define N 10005
using namespace std;
bool isnode[N];
int fa[N];
void init()
{
    for(int i=0;i<N;i++){
        isnode[i]=false;
        fa[i]=i;
    }
}
int find(int x)
{
    if(x==fa[x]) return x;
    else return find(fa[x]);
}
//只有根结点的入度为0,其余为1;
int main()
{
    int s,e,k=0;
    bool flag=true;
    init();
    while(cin>>s>>e){
        if(s==-1&&e==-1) break;
        if(s==0&&e==0){//输入处理
            k++;
            bool f=false;
            int cnt=0;
            for(int i=0;i<N;i++){
                if(isnode[i]) f=true;
                if(isnode[i]&&fa[i]==i) cnt++;
            }
            if(f==false) cout<<"Case "<<k<<" is a tree.\n";
            else if(cnt==1&&flag) cout<<"Case "<<k<<" is a tree.\n";
            else cout<<"Case "<<k<<" is not a tree.\n";
            //初始化
            flag=true;
            init();
        }else{
            isnode[s]=true;
            isnode[e]=true;
            int f1=find(s);
            int f2=find(e);
            if(fa[e]!=e&&fa[e]!=s) {
                flag=false;        //说明入度大于1
                continue;
            }
            if(f1==f2) flag=false;  //有环
            else fa[e]=s;
        }
    }
    return 0;
}

文章参考胡凡的《算法笔记》。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值