zoukankan      html  css  js  c++  java
  • 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代码如下:

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 typedef unsigned int ui;
      4 typedef long long ll;
      5 typedef unsigned long long ull;
      6 #define pf printf
      7 #define mem(a,b) memset(a,b,sizeof(a))
      8 #define prime1 1e9+7
      9 #define prime2 1e9+9
     10 #define pi 3.14159265
     11 #define lson l,mid,rt<<1
     12 #define rson mid+1,r,rt<<1|1
     13 #define scand(x) scanf("%llf",&x) 
     14 #define f(i,a,b) for(int i=a;i<=b;i++)
     15 #define scan(a) scanf("%d",&a)
     16 #define mp(a,b) make_pair((a),(b))
     17 #define P pair<int,int>
     18 #define dbg(args) cout<<#args<<":"<<args<<endl;
     19 #define inf 0x3f3f3f3f
     20 inline int read(){
     21     int ans=0,w=1;
     22     char ch=getchar();
     23     while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
     24     while(isdigit(ch))ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
     25     return ans*w;
     26 }
     27 const int maxn=1e5+10;
     28 int n,m,t;
     29 int head[maxn],nxt[maxn],low[maxn],sccno[maxn],dfn[maxn],id;
     30 struct node{
     31     int u,v;
     32 }p[maxn*10];
     33 int e=0;
     34 int cnt;
     35 stack<int> sk;
     36 void addedge(int u,int v)
     37 {
     38     p[e].u=u;
     39     p[e].v=v;
     40     nxt[e]=head[u];
     41     head[u]=e++;
     42 }
     43 void tarjan(int u)
     44 {
     45     low[u]=dfn[u]=++id;
     46     sk.push(u);//将结点编号压栈 
     47     for(int i=head[u];~i;i=nxt[i])
     48     {
     49         int v=p[i].v;
     50         if(!dfn[v])
     51         {
     52             tarjan(v);
     53             low[u]=min(low[u],low[v]);
     54         }
     55         else if(!sccno[v])//回退边且边终点没有标记SCC 
     56             low[u]=min(low[u],low[v]); 
     57     }
     58     if(low[u]==dfn[u])
     59     {
     60         cnt++;//连通分量的数量增加,对每个点这个if语句只执行一次 
     61         while(1)
     62         {
     63             int v=sk.top();
     64             sk.pop();
     65             sccno[v]=cnt;
     66             if(u==v)break;
     67         }
     68      } 
     69 }
     70 int main()
     71 {
     72     freopen("input.txt","r",stdin);
     73     //freopen("output.txt","w",stdout);
     74     std::ios::sync_with_stdio(false);
     75     while(scanf("%d%d",&n,&m)==2)
     76     {
     77         if(n==0&&m==0)break;
     78         e=0;cnt=0;
     79         mem(head,-1);mem(nxt,-1);
     80         while(!sk.empty())sk.pop();
     81         int x,y;
     82         f(i,1,m)
     83         {
     84             x=read();
     85             y=read();
     86             addedge(x,y);
     87         }
     88         mem(dfn,0);
     89         id=0;
     90         mem(sccno,0);
     91         mem(low,0);
     92         f(i,1,n)
     93         {
     94             if(!dfn[i])
     95                 tarjan(i);
     96         }
     97         f(i,1,n)dbg(low[i]);
     98         if(cnt==1)pf("Yes
    ");
     99         else pf("No
    ");
    100      } 
    101 } 
  • 相关阅读:
    Bootstrap的模态框中使用kindeditor无法编辑的问题
    resource is out of sync withthe file system
    Model报错:指定的存储区提供程序在配置中找不到 的解决
    两极——关于未来云计算的瞎想
    c#动态加载dll并调用dll中类的方法
    委托与事件的IL简单分析
    php搭建与网页服务器故障排查
    某AOP文章的注解……关于上下文和remoting
    终于有了我的blog了
    使用C#编写JavaScript
  • 原文地址:https://www.cnblogs.com/randy-lo/p/12588025.html
Copyright © 2011-2022 走看看