2.3 搜索与图论(最小生成树和二分图)

 

 1. 朴素版Prim算法

跟朴素dijkstra有点像,但是每次更新的是每个点到已收录的点的集合的距离,而不是到起点的距离。

先迭代n次,每次找到距离集合距离最小的点,将其收录、更新res,然后用其更新其余点到集合的距离

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510 , INF = 0x3f3f3f3f;
int g[N][N] , dist[N];
bool st[N];
int n , m;

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] and (t == -1 or dist[j] < dist[t]))
                t = j;
        
        st[t] = true;
        if (i and dist[t] == INF) return INF;  //如果不是第一次遍历并且该点到集合的距离为无穷
        if (i) res += dist[t];
        
        for (int j = 1 ; j <= n ; j ++)  //更新其余点到集合的距离
            dist[j] = min(dist[j] , g[t][j]);
    }
    return res;
}

int main()
{
    cin >> 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 cout << t << endl;
    
    return 0;
}

2. Kruskal算法

要用到并查集的知识。将所有边储存起来,按照边的权重进行排序,每次取出最短的边,如果两个端点不同,就将该边读入,记录读入的点的数量的cnt++

最终如果cnt < n - 1,说明有点没有读入,输出impossible

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
int p[N];
struct Edge{
    int a , b , c;
    bool operator < (const Edge & t) const  //重载小于号
    {
        return c < t.c;
    }
}edges[N];

int n , m;

int find(int x)  //并查集find
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m;
    
    for (int i = 1 ; i <= n ; i ++) p[i] = i;
    for (int i = 0 ; i < m ; i ++)
    {
        int a , b , c;
        scanf("%d%d%d" , &a , &b , &c);
        edges[i] = {a , b , c};  //储存所有边
    }
    
    sort(edges , edges + m);  //将所有边按权重排序
    
    int res = 0 , cnt = 0;
    for (int i = 0; i < m; i ++ )
    {
        auto t = edges[i];
        int a = find(t.a) , b = find(t.b) , c = t.c;
        
        if (a != b)  //每次找出最短的边,如果二者祖宗节点不同
        {
            res += c;
            cnt ++;
            p[a] = b;
        }
    }
    if (cnt < n - 1) puts("impossible");  //如果读入的点少于n - 1个
    else cout << res << endl;
    
    return 0;
}

3. 二分图

二分图当且仅当图中没有奇数环

3.1 染色法求二分图

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010, M = 200010;
int h[N] , e[M] , ne[M] , idx;
int color[N];  //染色,1 or 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 t)
{
    color[u] = t;  //要染色!
    for (int i = h[u] ; i != -1 ; i = ne[i])
    {
        int j = e[i];
        if (!color[j])
        {
            if (!dfs(j , 3 - t)) return false;  //如果该点染色失败
        }
        if (color[j] == t) return false;  //如果该点跟u点颜色一样
    }
    return true;
}


int main()
{
    cin >> n >> m;
    memset(h , -1 , sizeof h);
    
    while (m -- )
    {
        int a , b;
        scanf("%d%d" , &a , &b);
        add(a , b) , add(b , a);  //无向图
    }
    bool flag = true;
    for (int i = 1 ; i <= n ; i ++)
    {
        if (!color[i])
        {
            if (!dfs(i , 1))
            {
                flag = false;
                break;
            }
        }
    }
    if (flag) puts("Yes");
    else puts("No");
    return 0;
}

 

posted @ 2022-05-21 11:33  乐池  阅读(31)  评论(0)    收藏  举报