tarjan原理,割点,桥

来源:互联网 发布:政府部门域名申请 编辑:程序博客网 时间:2024/05/22 02:00
tarjan原理

tarjan是图论中常用的算法,用于求图中的强连通分量,割点等
这里先讲强连通分量的做法

强连通分量的定义:
在一个有向图的子图中,任意两个点相互可达,也就是存在互通的路径,那么这个子图就是强连通分量(或者称为强连通分支)。如果一个有向图的任意两个点相互可达,那么这个图就称为强连通图。
tarjan基于dfs。而且每个强连通分量恰好是深搜树的一颗子树。
如果u是某个强连通分量的根,那么:
(1)u不存在路径可以返回到它的祖先
(2)u的子树也不存在路径可以返回到u的祖先。
DFN[i]标记i这个点被访问到的时间
low[i]表示i这个点直接或者间接可以到达的点里面最早被访问到的点的时间(实际上就是同一个强连通分量里的根)
步骤:
从第1个点(u)开始搜索,刚开始要把DFN[u]和low[u]赋值为被访问到的时间, 每次都去遍历这个点关联的边<u,v>:
如果v不在栈里面,继续去递归搜索v,等到回溯以后,就要对low[u]判断,如果u的子树能到达更早的点,那么就把low[u]赋值为low[v[i]]。
如果v已经在栈里面了,此时形成一个环,那么此时对于low[u]的修改就是:

这样很明显,只有强连通分量的根的DFN值和low值是相等的,其他的点都进行过修改,所以如果DFN[i]==low[i],说明我们找到了一个强连通分量的根,要得到这个强连通分量,只要把相应元素出栈就行了。
由于每个点只访问1次,每条边也是1次,所以tarjan算法的时间复杂度是O(n+m)。

代码:
#include<stdio>  #include<cstring>    const int maxn=10005;  int DFN[maxn];//记录每个点被访问到的时间  int low[maxn];//记录点可以直接或间接到达的最早被访问到的点(也就是那个强连通分量的根)  int stack[maxn];  int sccnum[maxn];//标记每个点属于第几个强连通分量  bool instack[maxn];  int sccNum;//强连通分量的数目  int top;  int index;  int n;    struct node  {      int to;      int next;  }edge[10*maxn];  int head[maxn];  int tot;    void addedge(int from,int to)  {    edge[tot].to=to;      edge[tot].next=head[from];      head[from]=tot++;  }   void tarjan(int i)  {      DFN[i]=low[i]=++index;//刚刚搜到这个点,DFN和low都赋值为被访问到的时间      stack[top++]=i;//入栈      instack[i]=1;      for(int j=head[i];j!=-1;j=edge[j].next)      {          if(!DFN[edge[j].to])//如果没有被访问过          {              tarjan(edge[j].to);            //这个时候low可能要修改,值为i或者i的子树可以到达的最早被访问到的点的时间              if(low[i]>low[edge[j].to])                  low[i]=low[edge[j].to];          }          else if(instack[edge[j].to])//已经在栈          {              if(low[i]>DFN[edge[j].to])                  low[i]=DFN[edge[j].to];          }      }      if(DFN[i]==low[i])//找到根      {          sccNum++;          int v;          do         {              v=stack[--top];              sccnum[v]=sccNum;              instack[v]=0;//标记出栈          }while(v!=i);      }  }    void solve()  {      memset(DFN,0,sizeof(DFN));      memset(instack,0,sizeof(instack));      index=0;      sccNum=0;      top=0;      for(int i=1;i<=n;i++)          if(!DFN[i])              tarjan(i);  }    int main()  {      int m,a,b;      while(~scanf("%d%d",&n,&m))      {          if(n==0 && m==0)              break;          memset(head,-1,sizeof(head));          tot=0;          for(int i=0;i<m;i++)          {              scanf("%d%d",&a,&b);              addedge(a,b);          }          solve();          if(sccNum==1)              printf("Yes\n");          else              printf("No\n");      }      return 0;  }



基于强联通分量,还可以扩展出一些其他的算法:
1. 割点:
定义:在一张无向图中,如果去掉某个顶点以及和这个顶点相关联的边,使得整个图的连通分支数增  加,那么这个点就是一个割点.
tarjan求割点:
若一个节点为割点,一定满足以下两个条件之一:
    1).u是dfs搜索树的根,并且u含2棵及2棵以上的子树,即不同子树的节点要想联通必须经过u,那么把u删去图中的连通分支一定增加。
    2).u不是dfs搜索树的根,并且有不等式 low[v]>=DFN[u],其中(u,v)是树枝边(即v通过u延伸开去),因为v一定不会到达时间比u小的点,否则low[v]一定会小于dfn[u],所以v要达到祖先一定要经过u。
代码:
割点.md

2.桥:
    定义:
     在一张无向图中,如果去掉边(u,v)使得图的连通分支数增加,那么边(u,v)便称为桥.
     类似于割点,只不过是把点变为一条边而已。边(u,v)是无向图的桥当且仅当(u,v)满足 low[v]>DFN[u],另外要对于u,v间有重边进行特判。
代码:
void tarjan(int u,int root,int fa)    {        DFN[u]=low[u]=++index;        instack[u]=1;        int cnt=0;        for(int i=head[u];i!=-1;i=edge[i].next)        {            int v=edge[i].to;            if(!instack[v])            {                tarjan(v,root,u);                cnt++;                if(low[u]>low[v])                    low[u]=low[v];                if(u==root && cnt>1)                    cut_point[u]=1;                else if(u!=root && low[v]>=DFN[u])                    cut_point[u]=1;            }            else if(v!=fa && low[u] > DFN[v])                low[u]=DFN[v];        }



3.双联通分量:
在无向连通图中,如果删除该图的任何一个结点都不能改变该图的连通性,则该图为双连通的无向图。一个连通的无向图是双连通的,当且仅当它没有关键点。换言之,双连通分量里任何2个顶点之间都至少有2条不相交的路径
求法类似于求强联通分量,只不过是在无向图中。
代码:
void tarjan(int u,int fa)  {      instack[u]=1;      DFN[u]=low[u]=++index;      for(int i=head[u];i!=-1;i=edge[i].next)      {          int v=edge[i].to;          if(fa==edge[i].id)              continue;          if(!instack[v])          {              tarjan(v,edge[i].id);              low[u]=min(low[u],low[v]);          }          else              low[u]=min(DFN[v],low[u]);      }  }




原创粉丝点击