最小生成树:即最小权重生成树的简称
一个有n个结点的连通图的生成树是原图的一个极小的连通子图,且包含原图中的所有n个结点,
并且有保持图连同的最少的边
朴素Prim(V,E,W)V=(1,2,..N)
贪心策略
算法思路:
1.将第一个结点放入s集合//可以直接循环里
2、while(V-S!=NULL){//还有结点没有进到s集合的情况下(执行O(n))
从 V-S中选择j号结点(j满足它到S中某个结点的权值是所有连接V-S与S那些边里面最小的)
将j挑到S集合里O(n)
}
算法步骤
1.初始化d[i]->0x3f3f3f3f
2.执行n次(即V-S)
{
3.找到离s集合最近的点t,
4.用t更新其他点到集合的距离
}
/*给定n个点m条边的无向图,图中可能存在重边和自环
边权可能为负数,求最小生成树的树边权重之和,如果
不存在则输出impossible
*/
/*4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
6
*/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=510;//稠密图
int g[N][N];//第一维表示起点,第二维表示终点,其值表示边权
bool s[N];//最小生成树的集合
const int INF=0x3f3f3f3f;
int d[N] ;//记录从一个点到另一个点的最小
int n,m;//n个点m条边
int Prim()
{
memset(d,0x3f,sizeof d);//将距离初始化为正无穷
int res=0;//记录最小生成树的权重之和
for(int i=0;i<n;i++){//遍历n次
int t=-1;// 用来找到里s集合最近的结点编号
for(int j=1;j<=n;j++)//编号从1开始
if(!s[j]&&(t==-1)||(d[t]>d[j]))//如果j这个点不在最小生成树集合里并且(还没有找到一个最近的结点或者有一个更近的结点)
t=j;
if(i&&d[t]==INF) return INF;//如果不是第一个点或者没有找到t,即说明图不连通
if(i) res+=d[t];
for(int j=1;j<=n;j++)d[j]=min(d[j],g[t][j]);//这个点到集合的距离
s[t]=true;
}
return res;
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof g);
for(int i=0;i<m;i++)
{
int x,y,z;
cin>>x>>y>>z;
g[x][y]=g[y][x]=min(g[x][y],z);//无向边处理,重边则保留权重最小的边权
}
int t=Prim();
if(t==INF) puts("impossible");
else printf("%d",t);
return 0;
}
Kruskal 算法
步骤:
1.按长度从小到大对边排序
2.枚举当前最小边e,如果e与T(最小生成树的集合)的边不构成回路,就把e加入树T,否则跳过e,直到选择了n-1条边为止
/*给定n个点m条边的无向图,图中可能存在重边和自环
边权可能为负数,求最小生成树的树边权重之和,如果
不存在则输出orz
*/
/*4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
6
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200020;
struct edge{
int x,y,z;
bool operator<(const edge &Z)const
{
return z< Z.z;
}
}edge[N];
int p[N];
int find(int x)
{
if(x!=p[x])p[x]=find(p[x]);
return p[x];
}
int n,m;
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++)
cin>>edge[i].x>>edge[i].y>>edge[i].z;
sort(edge,edge+m);
for(int i=1;i<=n;i++)p[i]=i;
int cnt=0,res=0;
for(int i=0;i<m;i++)
{
int x=edge[i].x,y=edge[i].y,z=edge[i].z;
x=find(x),y=find(y);
if(x!=y) {
p[x]=y;
res+=z;
cnt++;
}
}
if(cnt<n-1) puts("orz");
else cout<<res;
return 0;
}
二分图:
二分图又称作二部图,是图论中的一种 特殊模型 。. 设G= (V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集 (A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集 (i in A,j in B),则称图G为一个二分图。
染色法:判断是否为二分图
一个图是否是二分图当且仅当这个图是否可以被二染色
二分图当且仅当图中不含奇数环
1.遍历每个点
2.如果遇到一个点为染色,将与这个点连通的所有点染色 dfs(i,1);//第几个点,染成什么颜色
/*判定二分图
n个点m个边的无向图,图中可能存在重边和自环
判断其是否为二分图
n m
接下来m行表示,u,v之间有一条边
输出yes或者no*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010,M=200010;
int n,m;
int h[N],e[M],ne[M],idx;
int color[N];
bool flag;
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;//染色
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
if(!color[j])//如果当前没染色
{
if(!dfs(j,3-c)) return false;
}
else if(color[j]==c)return false;//相邻的不能染成同一种颜色
}
return true;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b),add(b,a);
}
flag=true;//判断染色是否有矛盾
for(int i=0;i<n;i++)
if(!color[i])//如果没有染色
{
if( !dfs(i,1))//返回false则表示有矛盾
{
flag=false;
break;
}
}
if(flag)puts("Yes");
else puts("No");
return 0;
}
匈牙利算法是一种在多项式时间内求解任务分配问题的组合优化算法。换句话说就是,在可以接受的时间内去做匹配
M交错路径的增广
求二分图的最大匹配
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=520,M=100010;
int n1,n2,m;
int h[N],e[M],ne[M],idx;//邻接表
int match[N];//右边对应的点 下标为右边结点,值为0表示没有匹配左边,1表示匹配左边
bool st[N];//判重
bool find(int x)
{
for(int i=h[x];i!=-1;i=ne[i])//枚举相连的边
{
int j=e[i];//得到右边编号
if(!st[j])//如果没有匹配过
{
st[j]=true;
if(match[j]==0||find(match[j]))//右边j这个点还没匹配或者可以空出来
{
match[j]=x;
return true;
}
}
}
return false;
}
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
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++)//遍历右边的结点
{
memset(st,false ,sizeof st);//表示还没开始匹配
if(find(i))res++;//如果i结点很成功匹配,匹配数++
}
printf("%d\n",res);
return 0;
}