最小生成树、二分染色与匹配算法

最小生成树、二分染色与匹配算法

分类:算法

标签:图论算法/最小生成树/二分染色/二分匹配

最小生成树问题

我们从一个带权无向图中取出一个将所有节点连通的、不存在环路的子图,叫做生成树。

其中,所有边权重和最小的生成树叫做最小生成树。

P算法(Prim)

对于稠密图,我们可以使用Prim算法计算最小生成树的权重和。

使用邻接矩阵d储存图,dist数组维护在生成树中的点中所有能走到i的边中的最小权重,st记录已经在生成树中的节点

每次取出不在生成树中的最小权值边,如果在第一次取之后某次取时取到了极大权重边,说明无法将所有节点纳入生成树。

在第一次后,每次将该边的权重加入结果,并用该点更新到其他点的边权。

  • 时间复杂度:\(O(n^2)\)
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510,INF = 0x3f3f3f3f;
int d[N][N],dist[N];
bool st[N];

int n,m;
int prim(){
    memset(dist,INF,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;
        st[t] = true;
        if(i && dist[t]==INF)return INF;
        if(i) res += dist[t];
        
        for(int i=1;i<=n;i++)dist[i] = min(dist[i],d[t][i]);
    }
    return res;
}

int main(){
    memset(d,INF,sizeof d);

    scanf("%d%d",&n,&m);
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        d[a][b] = d[b][a] = min(d[a][b],c);
    }
    
    int r = prim();
    if(r == INF)puts("impossible");
    else printf("%d\n",r);

    return 0;
}

K算法(Kruskal)

我们将所有的边储存在edges数组中,将其按边权大小从小到大排序。

用并查集维护两个节点是否在同一个集合中,如果不属于同一个集合,那么将这条边纳入生成树,同时合并两点所在集合,同时,使用计数变量维护执行合并操作的次数。当算法结束时,如果合并次数小于n-1次,声明存在节点无法纳入生成树。

  • 时间复杂度:\(O(m\log{m})\)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010,M = 200020;
//并查集
int s[N];
int root(int x){
    if(x!=s[x])s[x] = root(s[x]);
    return s[x];
}
int n,m;
struct edge{
    int a,b,w;
    //按权排序
    bool operator<(const edge& W)const{
        return w<W.w;
    }
} ed[M];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>m;
    for(int i=0;i<m;i++){
        cin>>ed[i].a>>ed[i].b>>ed[i].w;
    }
    sort(ed,ed+m);
    
    int res = 0, cnt =0;
    //初始化并查集
    for(int i=1;i<=n;i++)s[i]=i;
    
    for(int i=0;i<m;i++){
        int a = ed[i].a, b = ed[i].b, w = ed[i].w;
        if(root(a)!=root(b)){
            s[root(a)] = b;
            res+=w;
            cnt++;
        }
    }
    
    if(cnt < n-1)cout<<"impossible"<<endl;
    else cout<<res<<endl;
    
    return 0;
}

二分图

如果一个无向图\(G\)中不存在包含奇数个节点的环,那么该图称为二分图。

染色法判断二分图

使用两种颜色为二分图染色,保证相连的节点不使用相同的颜色,如果成功将所有点染色,那么该图为二分图。

  • 时间复杂度:\(O(n)\)
#include<iostream>
using namespace std;
const int N = 100010,M = 200020;

int h[N],e[M],ne[M],w[M],idx = 0;
void add(int a, int b){
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
int n,m;
int clr[N];
bool dfs(int u, int c){
    clr[u] = c;
    for(int i=h[u];i!=-1;i=ne[i]){
        int j = e[i];
        if(!clr[j]){
            if(!dfs(j,3-c))return false;
        }
        else if(clr[j] == c)return false;
    }
    return true;
}
#include<cstring>
int main(){
    memset(h,-1,sizeof h);
    int n,m;
    cin>>n>>m;
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b);add(b,a);
    }
    
    bool flg = true;
    for(int i =1;i<=n;i++){
        if(!clr[i]){
            if(!dfs(i,1)){
                flg = false;
                break;
            }
        }
    }
    if(flg)cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
    
    return 0;
}

最大匹配问题

对于一个二分图,我们每次取出相连的两个点,称为一次匹配。称匹配的点的对数称为匹配数。

计算最大匹配数的问题称为二分图最大匹配问题。

匈牙利算法

对于一个二分图,假定我们要将编号为1-n1的节点与编号为1-n2的节点匹配,通过输入构建边,例如,输入a,b表示1-n1中的a号节点能够与1-n2中的二号节点匹配。实际上,我们可以借助二分图染色算法来解决。

然后,我们枚举1-n1的每个节点,如果其能够被匹配的节点被占用,那么递归地将匹配到该节点的节点匹配到下一个节点。

  • 时间复杂度:\(O(n)\)
#include<iostream>
#include<cstring>
using std::memset;
using std::cin;
using std::cout;
using std::endl;

const int N = 510,M = 100010;
//链表存图
int h[N],e[M],ne[M],idx=0;

//记录与j匹配的节点match[j]
int match[N];
//处理被占用的节点,每次枚举时清空
bool st[N];
void add(int a, int b){
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

bool pair(int x){
    for(int i=h[x];i!=-1;i = ne[i]){
        int j = e[i];
        if(!st[j]){
            //预匹配节点j
            st[j] = true;
            //如果j未被匹配或者match[j]能被匹配到下一个可选节点,则匹配成功
            if(match[j] == 0 || pair(match[j])){
                match[j] = x;
                return true;
            }
        }
    }
    return false;
}

int main(){
    memset(h,-1,sizeof h);
    int n1,n2,m;
    cin>>n1>>n2>>m;
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    
    int res = 0;
    for(int i=1;i<=n1;i++){
        memset(st,false,sizeof st);
        //匹配成功
        if(pair(i))res++;
    }
    
    cout<<res<<endl;
    return 0;
}
posted @ 2022-02-25 10:42  Sarfish  阅读(116)  评论(0)    收藏  举报