图的连通性小结

来源:互联网 发布:云计算用在什么行业 编辑:程序博客网 时间:2024/04/26 05:02

无向图的一些概念:

割点:一个连通图G,如果删除某个点后使得图G分成若干个部分,那么这个点就是割点,一个连通图中可以存在多个割点亦可以没有割点

块:块经常和割点一起出现在题目中,块是图G的一个没有割点的的连通子图

割边:一个连通图中,若删掉一条边后图G就不在是连通图,那么这条边就是割边

缩点:就是将没有割边的连通子图看成一个点,这个连通子图中的任意两点满足可达


点双连通:若图中的点割集的度大于1,那么这个图是点双连通的

边双连通:若图中的边割集的度大于1,那么这个图是边双连通的

不管是点双连通还是边双连通都统成为双连通

双连通分量:若图G‘是G的子图,G’是双连通的,且G‘不是任何双连通图的真子集,那么G’就是该图的极大双连通子图,这个极大双连通子图就是双连通分支,特殊的无向图点的双连通分支叫做块


求割点的算法:

采用tarjan的算法思想:从根节点开始搜索,每个节点有个最早的访问时间dfn[i]和该节点和它的子树节点能够更新到达的最早访问时间如图(1,2),(2,3)(3,1),假设我们的访问顺序为1--2--3,dfn[1]=1,dfn[2]=2,dfn[3]=3,因为节点3和节点1连接,那么节点3能够更新到比他更早被访问的节点1,那么low[3]=1,这样low[2]=1; 当low[v]>=dfn[u](v的u的子节点)时表示点u为一个割点,因为low[v]>=dfn[u]时表示u的子节点v没有其他路径到达比u更早的节点,也就是说节点u将他们断开了!! 这里判断一个点是否为割点时,当这个点为根节点时不能够用low[v]>=dfn[u]判断,因为  我们考察这个例子(1,2)(2,3)(3,1),我们如果按那个条件判断的话,根节点1也将会被我们判断为割点,但实际上这个图是双连通的,根本就不存在着割点,那么我们只需要特殊处理一下根节点即可,若根节点存在着多与两颗的子树时,那么这个根节点就比为割点!

求割点的模版:

const int N = 1005;  bool g[N][N];  int n, m;  int dfn[N],low[N],index,son,num[N];  int Min(int a, int b)  {      return a>b?b:a;  }  void Cutvedex(int u)  {      dfn[u]=low[u]=index++;      for(int v=n;v<=m;v++)      {          if(u==v)continue;          if(g[u][v])          {          if(dfn[v]==-1)          {              if(u==n)                  son++;              Cutvedex(v);              low[u]=Min(low[u],low[v]);              if((u!=n&&low[v]>=dfn[u])||(u==n&&son>1))                  num[u]++;          }          else low[u]=Min(low[u],dfn[v]);          }      }  }                
注意因为是边为无向边,所以边是双向的,那么在更新low[i]数组的时候可以允许直接向父节点更新,为什么呢?因为向父节点更新最多可以使节点节点的low[i]值降到和父节点相等,而判断是否为割点的条件为low[v]>=dfn[u],所以直接用父节点更新不影响! 当然不用父节点更新也是对的,可以参照求割边的写法!!! 注意一点,求无向图的割点时,图中的存在重边与否都不影响!!

求块算法: 求割点解决了,那么求块也就解决啦,只需要设一个栈来保存节点即可,当遇到low[v]>=dfn[u]时,那么我们栈,直到退出v为止,此时割点并未出栈,因为割点回属于多个块,所以我们得将割点分别加到各个块中去


求块的参考模版:

int dfn[N],low[N],index,block[N],top,st[N];void Cutblock(int u)  {      dfn[u]=low[u]=index++;      st[++top]=u;       for(int i=0;i<g[u].size();i++)      {          int v=g[u][i];          if(dfn[v]==-1)          {              Cutblock(v);              low[u]=Min(low[u],low[v]);              if(low[v]>=dfn[u])              {                  block[0]=0;                  while(1)                  {                      block[++block[0]]=st[top];                      if(st[top--]==v)break;                  }                  block[++block[0]]=u;                //  Countblock();              }          }          else low[u]=Min(low[u],dfn[v]);      }  }  
以上的变量均需要初始化,求块的时候并未考虑节点是不是为根节点,那么这样做为什么对呢?可以画图理解(具体证明不详) block[i]存放的块中的顶点block[0]表示顶点的个数

求割边和求缩点:类似于求割点就是判定条件没有了等号,low[v]>dfn[u],表示边u--》v,v不能通过其他路径到到达u,说明边u--v将它们割开,所以u--v是割边,那么缩点和求块类似设置一个栈来存储节点,当low[v]>dfn[u]是让节点出栈直到v出栈位置,那么这期间出栈的元素即为一个缩点

ps: 不存在重边的情况:因为是无向图求割边,那么每条边都是双向的,我们存这个图的时候都是将无向边拆成两条有向边存放,对于u--v这条无向边,若先是u-->v,然后到v的时候如果可以直接更新low[v]的话,那么这样就会使得割边的low[v]==dfn[u],那么这样就求不出来割边,所以我们不能让它直接更新其父节点!

如果有重边,重边也看成是割边的话,那么采用ps的方法可以解决

如果重边不看成是割边的话,那么我们第一次遇到一条v--u的边的时候不更新,如果第二次遇到那么则说明有重边,那么就更新


求割边和缩点模版:

int dfn[N],low[N],index,top,st[N],blg[N];  void Cutedge(int u, int fa)  {      int v,x;      low[u]=dfn[u]=index++;      st[++top]=u;      bool tag=false;      for(int i=head[u];i!=-1;i=edge[i].nxt)      {           v=edge[i].y;          if(v==fa&&tag==false){tag=true;continue;}//处理重边          if(dfn[v]==-1)          {              Cutedge(v,u);             low[u]=Min(low[u],low[v]);             if(low[v]>dfn[u])             {                 cnt++;                 do                 {                     x=st[top];                     top--;                     blg[x]=cnt;                 }while(x!=v);             }            }          else low[u]=Min(low[u],dfn[v]);      }        }  
注意以上模版并不是很好,因为如果要缩点的话,这样求出来的只有割边条数个点,那么少了一个,为什么会少一个呢? cnt表示的是割边的条数而不是边双连通子图的个数,因为还有一个边的双连通分量还在栈中! 如果想要直接将边的双连通分量直接求出来,那么我们可以这样写:

int dfn[N],low[N],index,top,st[N],blg[N];void Cutedge(int u, int fa){int v;low[u]=dfn[u]=index++;st[++top]=u;bool tag=false;for(int i=head[u];i!=-1;i=edge[i].nxt){     v=edge[i].y;if(v==fa&&tag==false){tag=true;continue;}if(dfn[v]==-1){Cutedge(v,u);   low[u]=Min(low[u],low[v]);}else low[u]=Min(low[u],dfn[v]);}if(low[u]==dfn[u]){cnt++;//表示割边的数目    do{v=st[top];top--;blg[v]=cnt;}while(u!=v);}}
这里的cnt就是边双连通子图的个数了,至于为什么画个图就理解了!!


有向图的强连通分量:

模版:

//强连通分量tarjan算法void Tarjan(int t){int i,j;DFN[t]=low[t]=++tnum;q[top++]=t;inq[t]=true;for(i=first[t];i!=-1;i=e[i].next){j=e[i].v;if(!DFN[j]){Tarjan(j);if(low[t]>low[j])low[t]=low[j];}else if(inq[j]&&low[t]>DFN[j])low[t]=DFN[j];}if(low[t]==DFN[t]){++cnt;do{num[cnt]++;belong[q[--top]]=cnt;inq[q[top]]=false;}while(q[top]!=t);}}void Tarjan1(){top=0,tnum=0,cnt=0;for(int i=1;i<=n;i++)if(!DFN[i])Tarjan(i);}









原创粉丝点击