割点(tarjan算法)

文章目录

    • 割点(无向图).
    • 总结:
      • 割点算法实现模拟
      • 题解
      • 思考
      • 再来一题

割点(无向图).

P3388 【模板】割点(割顶)题目链接:洛谷
tarjan遍历过程视频链接

总结:

1.图用dfs的遍历。dfs对图,就会形成树 (写dfs代码时刻要有""的思想)
2.Tarjan算法求非强连通图,主要用到了两个数组,dfn和low数组。
3.在dfs中如何加东西:
dfs(i)后面语句怎么写?每次dfs完成后,就是对前面的影响,
像栈一样。
4.无向图实际是 在无向树的基础上节点添加边。
连通图定义:
在无向图中,一个连通图中任意两点均可到达,称为连通图。
割点的定义:
在一个无向图里,去掉一个顶点,及其去掉该点的所有边,剩下的图
不连通,那么这个点就是个割点。
割边的定义: (不会不会,以后再补 。)
在一个无向图里,去掉一条边,图就不连通了。
举个栗子:下图的无向图的割点为:0,3。
割点(tarjan算法)_第1张图片

割点算法实现模拟

ps:以下两图来自上文视频链接截下来的图
在下图 上面数字dfn数组,下面数字low数组。
用了一个栈更好理解。
其中满足low[u]=dfn[u],退栈到u.
dfn中是时间戳是,1-2-3-4
然后回退到2 , 2-5
再回退到1, 1到6.
最后回退到1.结束。

强连通分量怎么就有了呢?
在退栈2时,进栈5,判断1时,low[5]=dfn[1];
之后在5中退出。
有个5-1,形成了一个1-2-5-1的环,这不就成了一个强连通分量了。

之后就是这么执行。

割点(tarjan算法)_第2张图片

下面图举例(方便理解后续的代码): 我画成树的样子,再在节点随意连,就成了无向图。
1号是该的根节点 :那么子节点是两个, 就不是三个了?
为什么呢?
原因是: 我们在遍历的时候深度搜索,搜到3时,3就搜到4,4被标记了,退回到1节点,4节点已经标记了,1节点就不会对4搜。
所以1节点的子节点是2个。

所以只要根节点的子节点有两个及以上,根节点就是割点。(这里的子节点要细心些)。
割点(tarjan算法)_第3张图片

割点(tarjan算法)_第4张图片

题解

样例输入:
上图。
题解代码如下
代码如下:
割点针对无向图!!

#include
#include
#include
using namespace std;
int read(){
    int s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}

const int maxn=2e5+3;
int low[maxn],dfn[maxn]; //dfn:"时间戳"
int visit[maxn]; //点的有无访问
int index;
vector<int> a[maxn]; //邻接表
int fa[maxn]; //
set<int> s;
inline void tarjan(int u){
	int sum=0;
	visit[u]=1;  
	dfn[u]=low[u]=++index;
	for(int i=0;i<a[u].size();i++){
		int v=a[u][i]; //u到v。
		if(!visit[v]){
			fa[v]=u; // 父亲节点用来判断起始点
			sum++;
			tarjan(v);
			//u后面的dfs完成了,执行下面语句时在v顶点要做的事都完成了
			//现在开始完成u的事,根据v的事完成u的事。
			low[u]=min(low[u],low[v]);
			//先把起始点排除.
			if(fa[u]!=u&&low[v]>=dfn[u]) s.insert(u);
		} else //if(v!=fa[u]) 
		low[u]=min(low[u],dfn[v]);
		//dfn[v] 会更好些,有点像能唯一确定他组成一个环的先祖吧
		//low[v] 写起也怪怪的
			}
			//根特判定的原因,根就没先祖了,也就用不了low,dfn判定了。
			//判断是无割点:子节点两个及以上就是割点。
	if(fa[u]==u&&sum>=2) s.insert(u);
}
int main (){
	int n,m;
	n=read();m=read();
	int u,v;
	for(int i=1;i<=m;i++){
		u=read();v=read();
		a[u].push_back(v);
		a[v].push_back(u);
	}
	for(int i=1;i<=n;i++){
		if(!visit[i]){
			fa[i]=i;//父节点赋初值
			tarjan(i);
			index=0;
		}
	}
	cout<<s.size()<<endl;
	for(set<int>::iterator i=s.begin();i!=s.end();i++){
		cout<<*i<<" ";
	}
	return 0;
}

思考

思考一下没s.insert(u),u会不会被重复的添加呢?
没错,当然会重复咯,不然为啥用set集合呢!,是吧
改一下tarjan函数
如下:
那就用ans数组记录下每个割点加了几次。

int ans[maxn];
inline void tarjan(int u){
	int sum=0;
	int num=0;
	visit[u]=1;
	dfn[u]=low[u]=++index;
	for(int i=0;i<a[u].size();i++){
		int v=a[u][i];
		if(!visit[v]){
			fa[v]=u;
			sum++;
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]) {num++;s.insert(u);
			//cout<
			}
		} else //if(v!=fa[u]) 
		low[u]=min(low[u],dfn[v]);
	}
	//if(fa[u]==u&&sum>=2) {num++;s.insert(u);}
	if(fa[u]!=u) num++;
	//if(u==1) cout<
	ans[u]=num;
}

ans数组结果如下 2 1 2 2 1 2
割点(tarjan算法)_第5张图片
咦!!!!细心的你发现了,咋就1加了两次呢???
原来顶点1是树的根呀(dfs嘛,就是图的遍历,那就树咯),1都是根了,就没有父节点。low[1]=dfn[1]=1; 所以会加一次。
这就是为啥在题解代码里会有对根节点的特判。
那么根节点怎么才能是割点呢。问到点子上了。

再来一题

2020小米选拔赛第一场D题:Router Mesh

题意:
无向图n点m边
删除任意一点后,有多少个连通图
思路
首先得知道图不一定是连通的喔。就会牵扯到有好几个连通图。
上文也讲到了ans数组。
嗯哼,派上用场啦 . . . . . . ...... ......
删除一点,那他的所有边也会被删掉。那会增加几个连通图呢??
那不得增加ans[i]个咯。

题解代码如下:

#include
#include
#include
using namespace std;
int read(){
    int s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
const int maxn=4e5+3;
int low[maxn],dfn[maxn];
int visit[maxn];
int index;
vector<int> a[maxn];
int fa[maxn];
set<int> s; 
int ans[maxn];
inline void tarjan(int u){
	int sum=0;
	int num=0;
	visit[u]=1;
	dfn[u]=low[u]=++index;
	for(int i=0;i<a[u].size();i++){
		int v=a[u][i];
		if(!visit[v]){
		//删去了对根节点的特判,根节点会进入s集合里,如果他不是割点就只进一次。
			fa[v]=u;
			sum++;
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]) {num++;s.insert(u);}
		} else //if(v!=fa[u]) 
		low[u]=min(low[u],dfn[v]);
	}
	//if(fa[u]==u&&sum>=2) {num++;s.insert(u);}
	//为啥要在这样还要num++呢,嗯。。。玄学。
	if(fa[u]!=u) num++;
	//if(u==1) cout<
	ans[u]=num;
}
int main (){
	int n,m;
	n=read();m=read();
	int u,v;
	for(int i=1;i<=m;i++){
		u=read();v=read();
		a[u].push_back(v);
		a[v].push_back(u);
	}
	int num=0;
	for(int i=1;i<=n;i++){
		if(!visit[i]){
			num++;
			fa[i]=i;
			tarjan(i);
			index=0;
		}
	}
	/*cout<::iterator i=s.begin();i!=s.end();i++){
		cout<<*i<<" ";
	}*/
	//cout<
	for(int i=1;i<=n;i++){
	//在这里ans[i]减了1 
	//对应了这条代码if(fa[u]!=u) num++;
	//也就是根节点不加,非根节点要加1。
		cout<<ans[i]-1+num<<" ";
	}
	return 0;
}

你可能感兴趣的:(图论,dfs,算法)