东北地区赛有个无向图判自环的题当时没苟出来,回来之后想了想用并查集可以实现,然后仔细查了一波还有哪些方法……
总结一波博客吧QAQ……
资源来源:
https://blog.csdn.net/ouyangruo/article/details/51057409
https://blog.csdn.net/acmdream/article/details/72983715
http://www.xuebuyuan.com/2229565.html
https://blog.csdn.net/lezg_bkbj/article/details/11299335
有向图:
法一(最佳):拓扑排序
每次找到入度为0的点,按顺序将其标记,若同时有多个则无所谓其间的先后顺序。
然后将这个点删掉,将以其为起点的边都删掉,即修改那些终点的点的入度即可。
然后,如果最后修改完所有的点的入度都是0,则没有环,否则有环。
想想一个1->2->3->1的有向图中的环,这样会导致1、2、3三个点的入度同时为1,找不到入度为0的点。
法二:
//似乎2600是邻接矩阵的极限了
//注意到,对于每个点i,i搜索完才会vis[i]=1,在此之前vis[i]=-1
//因此,vis[i]=-1的话,如果有环就会搜到自己从而判YES,如果搜到树的叶子结点,就会把树根改为vis[i]=1,继续搜别的
#include<cstdio>
#include<cstring>
int n,c,a[2600][2600],vis[2600];
int dfs(int v){
vis[v] = -1;
for(int i = 1; i <= n; i++)
{
if(a[v][i] != 0 && !vis[i]) {
dfs(i);
vis[i]=1;
}
if(a[v][i] != 0 && vis[i] == -1){
return 1*puts("YES");
}
}
return 0*puts("NO");
}
无向图:
法一:
//用父节点和子节点的转移 注意无向图父子节点是互通的
//记互通的这条边为e 那么子节点如果通过非e的边访问到“父节点”,说明有自环
//注意“父节点”可以不是直接基类,也可以是间接基类,即之前访问过的点,vis[i]=1的点
//无向无环图可以变成一棵树,理解成父向子的递归查询即可,返祖即是有环
#include <iostream>
using namespace std;
const int M=2005;
bool g[M][M],vis[M],flag;
//无权邻接矩阵 直接判有没有相连
int n,m;
bool dfs(int i,int pre)
{
vis[i]=1;
for(int j=1;j<=n;j++)
if(g[i][j])
{
if(!vis[j])return dfs(j,i); //父节点与子节点同真假
else if(j!=pre)return false; //如果访问过,且不是其父节点(无向图,子节点与父节点互通),那么就构成环
}
}
法二://HDU1272 小希的迷宫 补题
①判是否有自环,只需在合并两点之前判这两点是否已经来自同一个连通的集合
②判是否是连通图,只需看://这里采用的是(2)
(1)是否只有一个根节点,即是否能转化为一棵树
(2)在没有自环的情况下,边数=点数-1(树的定义)
#include <cstdio>
#include <cstring>
int par[100005]; //记录父节点
bool used[100005]; //是否使用过
void init()
{
for(int i=1;i<100005;++i)
par[i]=i;
}
int find(int x)
{
return x==find(x)?x:par[x]=find(par[x]);
}
void unit(int x,int y)
{
x=find(x);
y=find(y);
if (x==y)return;
par[y]=x;
}
bool same(int x,int y)
{
return find(x)==find(y);
}
int main()
{
int n,m,flag,i,t;
while (scanf("%d %d",&n,&m)!=EOF)
{
if (n==-1 && m==-1)break;
if (n==0 && m==0) //当一开始就输入0 0的话,要输出Yes,本题坑爹处
{
printf ("Yes\n");
continue;
}
memset(used,0,sizeof(used));
init();
unit(n,m);
used[n]=1,used[m]=1;//合并后,说明这两个点用过了
t=1; //t=点-边 刚开始时是2-1=1
flag=1; //flag表示当前所有点的根是否唯一,换言之,图是否是连通图
while (scanf("%d %d",&n,&m)!=EOF) //核心部分,相当于模拟加边的过程
{
if (n==0 && m==0)break;
if (used[n]==0)t++,used[n]=1;//加入一点,并标记
if (used[m]==0)t++,used[m]=1;
if (same(n,m))flag=0; //n与m在连这条边之前就已经连通
else t--; //若n和m的根节点不相同,则将他们合并,并且边数+1,相当于t-1
}
if (flag&&t==1)puts("Yes"); //若图中没有环,而且点数与边数相差1,则说明该图是符合的!
else puts("No");
}
return 0;
}
求无向图环的长度
以cf263D为例,求出一个长度不短于k+1的环
考虑dfs序,dfs完退栈
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,k;
int dfn[N],in[N],cnt;
int res[N],tot;
vector<int>E[N];
bool ok;
void dfs(int u,int fa)
{
if(ok)return;
in[u]=++cnt;
dfn[cnt]=u;
for(int v:E[u])
{
if(v==fa||in[v]==-1)continue;
if(!in[v])dfs(v,u);
else
{
if(ok)return;
if(in[u]-in[v]>=k)
{
for(int j=in[v];j<=in[u];++j)
res[++tot]=dfn[j];
ok=1;
return;
}
}
}
cnt--;
in[u]=-1;
}
int main()
{
int u,v;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;++i)
{
scanf("%d%d",&u,&v);
E[u].push_back(v);
E[v].push_back(u);
}
dfs(1,-1);
printf("%d\n",tot);
for(int i=1;i<=tot;++i)
printf("%d%c",res[i]," \n"[i==tot]);
return 0;
}