搜索与图论---Prim、Kruskal、染色法判断二分图、二分图的最大匹配

在这里插入图片描述

1.最小生成树

1.1普利姆算法(Prim)
1.1.1朴素版的普利姆算法(Prim) O(n^2)—稠密图

在这里插入图片描述
在这里插入图片描述

例题:
在这里插入图片描述
图解:
在这里插入图片描述
代码

#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 510,INF = 0x3f3f3f3f;

int n,m;
int g[N][N];//存储图
int dist[N];//存储各个节点到生成树的距离
bool st[N];//节点是否被加入到生成树中

int prim()
{
    memset (dist,0x3f,sizeof(dist));
    int res = 0;//最小生成树里面所有边长度之和
    for(int i = 0;i < n;i++)//每次循环选出一个点加入到生成树
    {
        int t = -1;
        for(int j = 1;j <= n;j++)//每个节点一次判断
            if(!st[j] && (t == -1 || dist[t] > dist[j]))//如果没有在树中,且到树的距离最短,则选择该点
                t = j;
        
        if(i && dist[t] == INF) return INF;//图是不连通的
        
        if(i) res += dist[t];
        //不是第一个点,
        //那么dist[t]表示当前的点和现在已经连好的生成树里面某一条边的长度
        for(int j= 1;j <= n;j++) dist[j] = min(dist[j],g[t][j]);
       
        
        st[t] = true;
    }
    
    return res;
}
int main()
{
    scanf("%d%d",&n,&m);//输入节点数和边数
    
    memset(g,0x3f,sizeof(g));//各个点之间的距离初始化成很大的数
    
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);//输入边的两个顶点和权重
        g[a][b] = g[b][a] = min(g[a][b],c);//存储权重,可能会有重边
    }
    
    int t = prim();//求最下生成树
    
    //所有点不连通
    if(t == INF)    puts("impossible");
    else printf("%d\n",t);
    
    return 0;
}

Prim算法
dist[i]距离设置为无穷大
dist[1]=0

for i in 0..n-1

  1. 找到不在s集合中,距离s集合最近的点t
  2. 将这个点t放入集合中
  3. 利用这个点t, 更新不在集合中的点

Prim算法与Dijkstra算法的区别
Dijkstra算法是更新不在集合中的点 离起点的距离

dist[j]=min(dist[j], dist[t]+g[t][j])

Prim是更新不在集合中的点 离集合S的距离

dist[j] = min(dist[j], g[t][j])

1.1.2堆优化版的普利姆算法(Prim)O(mlogn) —稀疏图
1.2克鲁斯卡尔算法(Kruskal)O(mlogm)

Kruskal算法 (解决稀疏图) O(mlog(m))O(mlog(m))

  1. 将所有边按照权重从小到大排序 O(mlog(m))O(mlog(m))
  2. 枚举每条边(a, b, 权重c) O(m)O(m)
  if a, b 两点不连通

        将a, b边加入集合中

第二步与 连通块中点的数量 相似

使用并查集,查询两个结点是否属于一个集合, 合并两个结点

例题
在这里插入图片描述

代码

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 200010;

int n,m;
int p[N];//p是并查集里面的p数组

//结构体存储所有边
struct Edge
{
    int a,b,w;
    
    //重载小于好,方便排序
    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[N];

//并查集模板
int find(int x)
{
    //如果x不是祖宗结点
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    scanf("%d%d",&n,&m);
    
    //将所以边存储下来
    for(int i = 0; i < m;i++)
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        edges[i] = {a,b,w};
    }
    //将所有边排序
    sort(edges,edges + m);
    
    //初始化并查集
    for(int i = 1;i <= n;i++)   p[i] = i;
    
    
    int res = 0,cnt = 0;
    //从小到大枚举所以边
    for(int i = 0;i < m;i++)
    {
        int a = edges[i].a,b = edges[i].b,w = edges[i].w;
        
        a = find(a),b = find(b);
        
        //判断两个点是否连通
        if(a != b)
        {
            p[a] = b;//合并
            //res存储最小生成树中所有树边的权重之和
            res += w;
            cnt++;//cnt存储当前加入多少条边
        }
    }
    
    //判断是否连通
    if(cnt < n - 1) puts("impossible");
    else printf("%d\n",res);
    
    return 0;
}

2.二分图

2.1染色法 O(n + m)

在这里插入图片描述
在这里插入图片描述
划分为两个集合,集合内部没有边。
在这里插入图片描述
代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010 * 2;
int e[N], ne[N], idx;//邻接表存储图
int h[N];
int color[N];//保存各个点的颜色,0 未染色,1 是红色,2 是黑色
int n, m;//点和边

void add(int a, int b)//邻接表插入点和边
{
    e[idx] = b, ne[idx]= h[a], h[a] = idx++;
}

bool dfs(int u, int c)//深度优先遍历
{
    color[u] = c;//u的点成 c 染色

    //遍历和 u 相邻的点
    for(int i = h[u]; i!= -1; i = ne[i])
    {
        int b = e[i];                   
        if(!color[b])//相邻的点没有颜色,则递归处理这个相邻点
        {
            if(!dfs(b, 3 - c)) return false;//(3 - 1 = 2, 如果 u 的颜色是2,则和 u 相邻的染成 1)
                                            //(3 - 2 = 1, 如果 u 的颜色是1,则和 u 相邻的染成 2)
        }
        else if(color[b] && color[b] != 3 - c)//如果已经染色,判断颜色是否为 3 - c
        {                                     
            return false;//如果不是,说明冲突,返回                   
        }
    }
    return true;
}

int main()
{
    memset(h, -1, sizeof h);//初始化邻接表
    cin >> n >> m;
    for(int i = 1; i <= m; i++)//读入边
    {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);
    }
    for(int i = 1; i <= n; i++)//遍历点
    {
        if(!color[i])//如果没染色
        {
            if(!dfs(i, 1))//染色该点,并递归处理和它相邻的点
            {
                cout << "No" << endl;//出现矛盾,输出NO 
                return 0;
            }

        }
    }
    cout << "Yes" << endl;//全部染色完成,没有矛盾,输出YES
    return 0;
}



2.2匈牙利算法 O(nm),实际运行时间一般远小于O(nm)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
最多确定多少对恋爱关系(关系非常正的那样)
在这里插入图片描述

匈牙利算法准则:待字闺中,据为己有;名花有主,求他放手。

例题:
在这里插入图片描述
代码实现:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510 , M = 100010;
int n1,n2,m; // n1男生,n2女生,m心仪线路
int h[N],ne[M],e[M],idx;
bool st[N];   // 作用:判重.男生(1-n1)找对象的时候是否已经考虑过这个女生
int match[N]; // 右边指向左边,match[j]=a,表示女孩j的现男友是a

void add(int a , int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// x来配对,能否找到另一半
bool find(int x) 
{
    // 遍历所有心仪女生
    for(int i = h[x] ; i != -1 ;i = ne[i])
    {
        int j = e[i];  // 获取我心仪女生编号都是啥(大数据系统main那里帮add的)
        if(!st[j])     // 这个女生我还没考虑过(考虑过就不要考虑了)
        {
            st[j] = true; // 那就考虑一下她
            if(!match[j]||find(match[j])) // 发现这个女生单身或她现男友还有备胎
            {
                match[j] = x; // 果断勾搭或者绿了她现男友
                return true;  // 成功配对
            }
        }
    }
    return false; // 心仪的女生都被别人配对了,我就只能单身了
}
int main()
{

    scanf("%d%d%d",&n1,&n2,&m);
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b); 
        add(a,b);
    }
    int res = 0;
    for(int i = 1; i <= n1 ;i ++) // 帮n1个男生找对象
    {  
        memset(st,false,sizeof st); // 保证每个女生被所有心仪她的男生都考虑一遍
        if(find(i)) 
          res++;   // 最多可以确定多少对恋爱关系
    }  

   printf("%d\n",res);
   return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Next---YOLO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值