最小生成树、二分染色与匹配算法
最小生成树、二分染色与匹配算法
分类:算法
标签:图论算法/最小生成树/二分染色/二分匹配
最小生成树问题
我们从一个带权无向图中取出一个将所有节点连通的、不存在环路的子图,叫做生成树。
其中,所有边权重和最小的生成树叫做最小生成树。
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;
}
本文来自博客园,作者:Sarfish,转载请注明原文链接:https://www.cnblogs.com/sarfish/articles/15934925.html

浙公网安备 33010602011771号