十六、双连通子图

1.计算双连通子图

所谓双连通子图指的是没有割点的连通子图,亦称块。这个子图中的任何一对节点至少存在两条不相交的路径,或者说要使这个子图不连通,至少要删除块内两个节点。


块是图中边的划分,即图中的每条边都包含在某个块中。
不同的两块不含公共边,不同的两个块最多有一个公共节点,此节点就是原图的割点。 

求点双连通分支

对于点双连通分支,实际上在求割点的过程中就能顺便把每个点双连通分支求出。建立一个栈,存储当前双连通分支,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。

模板:

program find_BCC;
const
  maxn=100;
  maxm=10000;
Type   //边类型
  edge=record
    u,v,next:integer;
  end;
var
  g:array[1..maxm] of edge;  //边表,静态连实现
  h:array[1..maxn] of integer;//节点头指针
  stack:array[1..maxm] of edge;//栈
  bcc:array[1..maxn,0..maxn] of integer;
  //双连通分量集合
  pre,low,bccNo:array[1..maxn] of integer;
  isCut:array[1..maxn] of boolean;
  //判断割点
  top,n,m,u,v,i,j,time,bccCnt,st:integer;
  //bccCnt表示双连通分量序号,st为栈顶指针

procedure findBCC(u,fa:integer);
var
  v,child,tmp:integer;
begin
  inc(time);  pre[u]:=time; low[u]:=time;
  tmp:=h[u];  child:=0;
  while tmp<>-1 do
    begin
      v:=g[tmp].v;
      if pre[v]=-1 then
        begin
          inc(child);
          push(g[tmp]);
          findBCC(v,u);
          low[u]:=min(low[u],low[v]);
          if low[v]>=pre[u] then
            begin
              iscut[u]:=true;
              inc(bccCnt); pop(g[tmp]);
            end;
        end
      else if (v<>fa) and (pre[v]<pre[u]) then
        begin
          push(g[tmp]);
          low[u]:=min(low[u],pre[v]);
        end;
      tmp:=g[tmp].next;
    end;
  if (child=1) and (fa=-1) then iscut[u]:=false;
end;

procedure pop(e:edge);
var tmp:edge;
begin
  repeat
    tmp:=stack[st];
    dec(st);
    if bccNo[tmp.u]<>bccCnt then
      begin
        inc(bcc[bccCnt,0]);
        bcc[bccCnt,bcc[bccCnt,0]]:=tmp.u;
        bccNo[tmp.u]:=bccCnt;
      end;
    if bccNo[tmp.v]<>bccCnt then
      begin
        inc(bcc[bccCnt,0]);
        bcc[bccCnt,bcc[bccCnt,0]]:=tmp.v;
        bccNo[tmp.v]:=bccCnt;
      end;
  until (tmp.u=e.u) and (tmp.v=e.v);
end;

procedure push(e:edge);
begin
  inc(st);
  stack[st]:=e;
end;

procedure addedge(u,v:integer);
begin
  inc(top);
  g[top].u:=u;
  g[top].v:=v;
  g[top].next:=h[u];
  h[u]:=top;
  inc(top);
  g[top].u:=v;
  g[top].v:=u;
  g[top].next:=h[v];
  h[v]:=top;
end;

begin
fillchar(h,sizeof(h),$ff);
  top:=0;
  readln(n,m);
  for i:=1 to m do
    begin
      readln(u,v);
      addedge(u,v);
    end;
  time:=0;
  bccCnt:=0;
  st:=0;
  fillchar(isCut,sizeof(iscut),false);
  fillchar(bccNo,sizeof(bccNo),0);
  fillchar(pre,sizeof(pre),$ff);
  fillchar(bcc,sizeof(bcc),0);
  for i:=1 to n do
    if pre[i]=-1 then
      findBcc(i,-1);
  writeln('BCC:',bccCnt);
  for i:=1 to bccCnt do
    begin
      for j:=1 to bcc[i,0] do
        begin
          write(bcc[i,j],' ');
        end;
      writeln;
    end;
  write('The cut point is:');
  for i:=1 to n do if iscut[i] then write(i, ' ');
  writeln;
end.

2.求边双连通分支

对于边双连通分支,求法更为简单。只需在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。

构造双连通图

一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。
统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。


下面有几道题:

POJ 3177  Redundant Paths
POJ 3352  Road Construction
POJ 3694  Network


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值