tarjan算法强连通分量的正确性解释+错误更新方法的解释!!!+hdu1269
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1269
以下内容为原创,转载请声明。
强连通分量SCC(Strongly Connected Component):一个图的子图,如果任何两个点都互相可达且满足最大性,该子图就是原图的SCC。
对于有向图的连通性,tarjan可以说是十分牛逼了,由于tarjan只需要一次dfs就能判断有向图中所有的SCC,所以他的复杂度是O(|V|+|E|),也就是把每条边和每个点都会检索一遍,是在线性时间能处理出所有的SCC的!下面简单说明求强连通分量的tarjan算法的过程以及正确性。
基础知识:
①、经dfs处理过的点的low值相同的一定属于同一个SCC,这个SCC中第一个被dfs访问的点也就是dfn=low的点一定就是这个SCC中每个点的low值,显而易见。
②、在dfs过程中,dfs进行最深处一定会进入某一个SCC,(尽管在过程中可能会进入其他SCC),下面我会解释
③、标记SCC Number的过程可以用栈实现,因为dfs的过程就是递归,递归和栈非常相似

tarjan算法的流程:将每个访问的点入栈,更新low值,直到到达一个点,这个点low值等于dfn值,说明这个点一定是一个SCC的祖先,也就是第一个访问的点,此时这个点一定在栈的底部!!因为是通过dfs退出多次而不弹栈才得到这个low=dfn。我们只要不断弹出l这个SCC的点并标记就可以,直到弹到栈底。我们先看基础知识(2),上图中在访问3的时候进入了4、5结点,也就是其他SCC,到了5的时候,由于low[5]=dfn[5]所以此时弹栈,发现就只有一个5属于这个SCC,4也是同样的过程,直到到了3,进入另一个分支6,这就是我基础知识(2)中的一定会在最终进入一个SCC而且栈底元素就是这个SCC的祖结点。
关于强连通分量处理回退边的方式,我的解释是这样的:
当一个点已经标记过dfn之后,而且这个点在之前没有标记过SCC(因为在dfs过程中会进入并处理其他SCC,若此时有边相连就无需更新,因为回退边所到点的low值一定小于该点的low)就用
①、low[u]=min(low[v],dfn[v]) ②、low[u]=min(low[v],low[u])
来更新low值,其实对于双连通分量来说,这两种更新方式的结果是一样的,但是第一种方法在原理上是错误的!!!!! 我来举一个例子,又是下面这张图,

对于第一种我们得出的结果是下面第一张图,而对于第二张我们得到的结果是下面第二张图,我们可以发现,根据tarjan算法的思想,处于同一个强连通分量中的点low值应该是相同的,但是第一种做法中的点的low值是不同的,但是他们得到的结果都是一样的,都是图是强连通的,为什么呢?


下面我将证明第一种做法得出正确结果的原因:
由于对于4,5号节点来说,他们的low值为3的时候,都与他们的dfn值不相同(dfn[4]=4&dfn[5]=5),所以他们就会一直在栈中,而栈底也一定是1号结点。4,5号结点没有机会弹出,直到dfs return到1号结点,此时才会弹栈,一直到弹出1号结点为止,这就把栈中的结点4,5弹出了,但是事实上他们的low值是更新错误的。尽管是错误的,但是中途dfs在访问其他SCC的时候(如果有的话,可以参考最上面那张图)已经把其他SCC的点都标记了,此时栈中剩下的只有这一个SCC!!所以最终的结果是相同的,但是!!这种更新方法是错误的!!因为在之后不能通过他们的不同的low值数量去查看SCC的数量,只能从每一次弹栈中给出的SCCNUMBER中得到SCC的信息。而且,这种做法跟tarjan的算法中“每个SCC”的low值是相同的是矛盾的。
看完我的文章能不能支持一下呢
hdu1269代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef unsigned int ui;
typedef long long ll;
typedef unsigned long long ull;
#define pf printf
#define mem(a,b) memset(a,b,sizeof(a))
#define prime1 1e9+7
#define prime2 1e9+9
#define pi 3.14159265
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define scand(x) scanf("%llf",&x)
#define f(i,a,b) for(int i=a;i<=b;i++)
#define scan(a) scanf("%d",&a)
#define mp(a,b) make_pair((a),(b))
#define P pair<int,int>
#define dbg(args) cout<<#args<<":"<<args<<endl;
#define inf 0x3f3f3f3f
inline int read(){
int ans=,w=;
char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-;ch=getchar();}
while(isdigit(ch))ans=(ans<<)+(ans<<)+ch-'',ch=getchar();
return ans*w;
}
const int maxn=1e5+;
int n,m,t;
int head[maxn],nxt[maxn],low[maxn],sccno[maxn],dfn[maxn],id;
struct node{
int u,v;
}p[maxn*];
int e=;
int cnt;
stack<int> sk;
void addedge(int u,int v)
{
p[e].u=u;
p[e].v=v;
nxt[e]=head[u];
head[u]=e++;
}
void tarjan(int u)
{
low[u]=dfn[u]=++id;
sk.push(u);//将结点编号压栈
for(int i=head[u];~i;i=nxt[i])
{
int v=p[i].v;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(!sccno[v])//回退边且边终点没有标记SCC
low[u]=min(low[u],low[v]);
}
if(low[u]==dfn[u])
{
cnt++;//连通分量的数量增加,对每个点这个if语句只执行一次
while()
{
int v=sk.top();
sk.pop();
sccno[v]=cnt;
if(u==v)break;
}
}
}
int main()
{
freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
std::ios::sync_with_stdio(false);
while(scanf("%d%d",&n,&m)==)
{
if(n==&&m==)break;
e=;cnt=;
mem(head,-);mem(nxt,-);
while(!sk.empty())sk.pop();
int x,y;
f(i,,m)
{
x=read();
y=read();
addedge(x,y);
}
mem(dfn,);
id=;
mem(sccno,);
mem(low,);
f(i,,n)
{
if(!dfn[i])
tarjan(i);
}
f(i,,n)dbg(low[i]);
if(cnt==)pf("Yes\n");
else pf("No\n");
}
}
tarjan算法强连通分量的正确性解释+错误更新方法的解释!!!+hdu1269的更多相关文章
- Tarjan算法--强连通分量
tarjan的过程就是dfs过程. 图一般能画成树,树的边有三种类型,树枝边 + 横叉边(两点没有父子关系) + 后向边(两点之间有父子关系): 可以看到只有后向边能构成环,即只有第三张图是强连通分量 ...
- Tarjan求强连通分量,缩点,割点
Tarjan算法是由美国著名计算机专家发明的,其主要特点就是可以求强连通分量和缩点·割点. 而强联通分量便是在一个图中如果有一个子图,且这个子图中所有的点都可以相互到达,这个子图便是一个强连通分量,并 ...
- 【学习整理】Tarjan:强连通分量+割点+割边
Tarjan求强连通分量 在一个有向图中,如果某两点间都有互相到达的路径,那么称中两个点强联通,如果任意两点都强联通,那么称这个图为强联通图:一个有向图的极大强联通子图称为强联通分量. 算法可以在 ...
- tarjan求强连通分量+缩点+割点以及一些证明
“tarjan陪伴强联通分量 生成树完成后思路才闪光 欧拉跑过的七桥古塘 让你 心驰神往”----<膜你抄> 自从听完这首歌,我就对tarjan开始心驰神往了,不过由于之前水平不足,一 ...
- tarjan求强连通分量+缩点+割点/割桥(点双/边双)以及一些证明
“tarjan陪伴强联通分量 生成树完成后思路才闪光 欧拉跑过的七桥古塘 让你 心驰神往”----<膜你抄> 自从听完这首歌,我就对tarjan开始心驰神往了,不过由于之前水平不足,一 ...
- CCF 高速公路 tarjan求强连通分量
问题描述 某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路. 现在,大臣们帮国王拟了一个修高速公路的 ...
- Tarjan求强连通分量、求桥和割点模板
Tarjan 求强连通分量模板.参考博客 #include<stdio.h> #include<stack> #include<algorithm> using n ...
- UESTC 901 方老师抢银行 --Tarjan求强连通分量
思路:如果出现了一个强连通分量,那么走到这个点时一定会在强连通分量里的点全部走一遍,这样才能更大.所以我们首先用Tarjan跑一遍求出所有强连通分量,然后将强连通分量缩成点(用到栈)然后就变成了一个D ...
- HDU 1827 Summer Holiday(tarjan求强连通分量+缩点构成新图+统计入度+一点贪心思)经典缩点入门题
Summer Holiday Time Limit: 10000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)T ...
随机推荐
- myecplise上将工程部署到应用下时,经常出现 An internal error occurred during: "Add Deployment". java.lang.NullPointEx
myecplise上将工程部署到应用下时,经常出现 An internal error occurred during: "Add Deployment". java.lang.N ...
- EventBus 及一些思考
EventBus 是 Android 开发的一种常用框架,其解耦的思维令人赞叹 从特性上来讲,其与 Android SDK中的BroadcastReceiver很像,二者都是注册,发送事件,反注册,都 ...
- linux学习--2.文件管理的基本命令
文件的基本操作 前言: 看完这篇图文我应该能保证读者在Linux系统下对文件的操作能跟用Windows环境下一样流畅吧,好了下面正文 正文: 基础知识: linux里共有以下几类文件,分别为目录(di ...
- python类变量与构造函数的使用
类变量:可在类的所有实例之间共享的变量 实例类对象:类的实例是调用类对象来创建的.如:par = Parent(),par就是类Parent的一个实例类对象. 实例变量(成员变量):同一个类对象可以创 ...
- 基于python openOPC的监控页面一
笔者涉猎的工业领域项目遇到一个需求,需要把底层设备(表记)的状态和运行数据集中放到一个监控画面进行展示,数据需要在界面端实时进行刷新,类似网友的例子,如下图(侵删) 数据需要实时主动刷新,笔者基于多年 ...
- http面试问题集锦
1.http的请求报文和响应报文? http请求报文:请求行(请求方法+url).请求头,请求体 http响应报文:状态行(http版本+状态码).响应头.响应体 2.常用的http请求类型? 请 ...
- 在eclipse的Java类文件中,右上角出现大写字母A代表什么
代表这个文件(类)是一个抽象类abstract的第一个字母:
- 三年前端,面试思考(头条蚂蚁美团offer)
小鱼儿本人985本科,软件工程专业,前端.工作三年半,第一家创业公司,半年.第二家前端技术不错的公司,两年半.第三家,个人创业半年.可以看出,我是个很喜欢折腾的人,大学期间也做过很多项目,非常愿意参与 ...
- OAuth 流程与发展总结 (1.0 => 1.0a => 2.0)
OAuth 流程与发展 (1.0 => 1.0a => 2.0) 概述 概述: 开放授权协议 作用: 允许第三方应用访问服务提供方中注册的终端用户的部分资源 下面是官方描述: [OAuth ...
- ES6的函数
1,带参数默认值的函数 JS函数有个独特的行为:可以接受任意数量的参数,而无视函数声明的形参数量.未提供的参数会使用默认值来代替.实际传递的参数允许少于或多于正式指定的参数. 在ES6中可以直接在形参 ...