#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>
#include<cmath>
using namespace std;
const int maxn = 500050;
int n,m,vs = 0,cnt = 0,tot = 0,a,b;
int loop[maxn],vis[maxn],f[maxn],Next[maxn],head[maxn],ver[maxn];
inline void add(int x,int y){
ver[++tot] = y; Next[tot] = head[x]; head[x] = tot;
}
inline int get(int x){
if(f[x] == x) return x;
return f[x] = get(f[x]);
}
inline void getloop(int x){
// 这时我必须要解释一下,这个算法事先进行一次遍历全图的操作,并且将每一个点的f数组都标记为遍历时他的上一个节点(也就是邻接表的起点)当遍历所有子树时,
// 环上的点也差不多遍历完了,当遍历完环上的点时(此时遍历的点为环上连接起点的点(也就是起点倒着数一个的点)),这时再遍历,就会开始递归的return ;
// 从而从这个点开始记录,一直到记录完整个环;
vis[x] = ++vs;//一个时间戳,如果这个数组对应为0,意味着未被遍历到;
for(int i = head[x]; i ;i = Next[i]){
int y = ver[i]; //正常思路,记录另一个端点
if(y == f[x]){// 1"当遍历完环上的点时,这时再遍历,就会开始递归的return" 的触发点,也是遍历到子树的叶节点后向返回到树根的触发点;因为“
// 每一个点的f数组都标记为遍历时他的上一个节点”,所以如果这条边i的结束点为这个节点的上一个节点,那么就是已经到了尽头了,这时将会continue,
// 而这时continue由于后面没有内容了;将会进行return
continue;
}
if(vis[y] != 0){// 2如果这个点(y)已经遍历过,那么进入这里; 注:由于这个点是过了上一个if的,所以它并不是叶节点;
if(vis[y] < vis[x]) continue; //2如果y点(y,这时的y为环的起点)已被遍历过了,而且是在x之前遍历的,这时就已经找到环了(关于y为环的起点)
//这是由于x点已经是遍历过所有子树(如果未遍历过所有子树,上一个if将不会放这个点进来)
//而这个y就是起点 这时进行一次continue操作,同样会引起return;之后环上的点会反复触发这个if从而一直向前倒;直到起点; 而以下的操作都是关于起点的;
loop[++cnt] = y;//3上面的那个if如果不触发,就意味着,y点在x点之后遍历这时已经倒回到了环的起点啊,那么,y就是环上的下一个点,(因为建立了双向边)
//所以这时这条以x为起点,y点为终点的边尚未便利,而且由于连接着起点和起点下一个点的边已被遍历过,那么y点就是起点倒着数一个的点;这时记录y;
for(; y!= x; y = f[y]){//并且以y为起点进行倒回,一边倒一边记录,直到起点;
loop[++cnt] = f[y];
}
}
else //1如果vis == 0 ,意味着这个点尚未被遍历到, 那么,对它的f数组进行更新,并且以她为起点便利;
{
f[y] = x,getloop(y);
}
// cout << "????????????"<<x<<endl;
}
return;
}
int main(){
cin >>n>>m;
for(int i = 1;i<= m;i++){
cin >> a>>b;
add(a,b); add(b,a);
}
for(int i= 1;i <= n;i++) f[i] = i;
getloop(0);
for(int i = 1;i <= cnt ;i++){
cout << loop[i] <<endl;
}
return 0;
}
基环树找环
最新推荐文章于 2022-07-31 16:52:05 发布