割点、割边

割点、割边一 割点 割边 双连通分支概念挂接点 Articulation 就是割点 CutVertex 桥 Bridge 就是割边 CutEdge 割点 v 为割点 则去掉 v 后 图的连通分支增加

大家好,欢迎来到IT知识分享网。

一、割点、割边、双连通分支概念

证明:假设双连通分支C1、C2,且有两个公共点v1、v2,因为双连通分支划分非桥边,所以v1与v2之间至少有两条边,因此假设v1与v2有两条边,如下图:

命题:两个双连通分支之间的一个公共点是割点。

证明:如果公共点不是割点,则将A点去除后,C1与C2仍然连通,因此必然存在u与v使得(u,v)属于E,因此当A存在时,u到A存在路径,A到v存在路径,u和v之间存在一条边,因此(u,v)与C1中任何一条边都在一个简单回路,(u,v)与C2中任何一条边都在一个简单回路,所以C1与C2合并。

 

命题:割点一定属于至少两个双连通分支。

证明:如果割点属于一个双连通分支,根据双连通分支定义,去掉任何一个点都不会让图不连通,与割点的定义矛盾。

命题:在一个双连通分支中至少要删除两个点才能够使图G不连通。

证明:双连通分支定义。Trivial。

命题:对于根顶点u,u为割点当且仅当u有至少两个儿子。

命题:对于非根顶点u,u为割点当且仅当u只要存在一个子顶点s,s的后裔(注意是后裔,不是真后裔)没有指向u的真祖先的反向边。

点将一个图分成了两部分,设从任一部分的任一点出发对图进行DFS遍历,当遍历递归到割点后,
就进入了图的第二部分。又因为每个点只能visit一次,所以第二部分的点不论怎样遍历再也回不
到第一部分了。当所有点(第二部分中)都被访问完后,才回溯到割点,再继续向上回溯。

 

在DFS的过程中,记录每个点u在DFS树中的标号n1(放在dep[u]中),以及该点所能到达的最小顺序
号n2(存在low[u]中)。注意:这个n2在求取时,是递归进行的,从u的子孙们的low值n2与u的祖先
们的dep值n1(此时u祖先的low值还未求出,dep相当于它的low值)中挑出最小的。这就给了u的儿子
们low值比u还小的机会。然而,如果u是割点,那么u孩子们的low值就决然 >= u 了。这也就成了
判断u是割点的方法。



 

至于割边,可以再判断u是否为割点的同时,顺便判断<u,u儿子i>是否为割边。只要满足low[i]>u
就行了。

 

另外,对于dfs起点就是一个割点的情况:如果不是割点,那它必然只有一个儿子(其他连接都被
dfs回溯掉了)。它必须是割点,才能保证它的几个儿子不被dfs回溯掉。

注意:想要记录割点u切去后增加的连通分量数目。不能简单的记录u的孩子数,而必须在判断u为
割点成立的地方进行统计。即有多少个证明了u是割点的孩子,它们就是u在切除后生成的新连通
分量。割点u的某些孩子不一定能证明u是割点,因为它可能与比u小的某个点相连,从而使自己的
low值比u还小,这与具体的dfs树有关,即遍历的顺序有关。 可见末尾的一组数据,对同一个图
的描述,由于建树的方式不同,导致3的儿子4,不能证明3是割点。从而使3的孩子数(3) != 3造就
的连通分量数2(删除3后,两棵子树成为连通分量)。







针对这点再强调一点:对根节点删掉自己后,就只剩新生的联通分量了。不同于枝节点,还有旧的
连通分量在。

 

汇总如下:
如果根结点有大于1个的儿子,那么根结点是割点。(这是防止)
如果对于点u的某个儿子v,有low[v] >= dep[u],那么u就是一个割点。
如果对于点u的某个儿子v,有low[v] > dep[u],那么(u,v)是一条割边。


 #include<iostream>
#include<algorithm>
using namespace std;

const int N = 110;
const int WHITE = 0,GREY = 1,BLACK = 2; //标记值
int map[N][N];
int col[N],low[N],dep[N];//color
int n,m;
bool tag[N];//标记点i是否割点

//求0-1图的割点
void dfs(int now,int father,int depth){

    col[now] = GREY;
    dep[now] = depth;
    int child = 0;
    for(int i=1;i<=n;i++){

        if(map[now][i]==0)continue;
       
        if(i != father && col[i] == GREY)
            low[now] = min(low[now], dep[i]);//low需要被初始化成大数
        if(col[i] == WHITE){

            dfs(i, now, depth+1);
            child = child + 1;
            low[now] = min(low[now], low[i]);

            if((now==1&&child>1)||(now!=1&&low[i]>=dep[now])) //判割点
                tag[now] = 1;//注意:需要记录该割点增加几个联通分量的操作需要在这里cnt++
        }
    }
    col[now] = BLACK;
}

void init(){

    memset(map,0,sizeof(map));
    memset(col,0,sizeof(col));
    memset(tag,0,sizeof(tag));
    for(int i=0;i<=n;i++)low[i]=INT_MAX; //low应该被初始化成大值
}

int main(){

    int a,b;
    cin>>n>>m;
    init();
    for(int i=0;i<m;i++){

        cin>>a>>b;
        map[a][b] = map[b][a] = 1;//无向图
    }
   
    dfs(1,1,0);//dfs(root,root,0)的形式调用就行了
    int ans=0;
   
    for(int i=1;i<=n;i++)
        if(tag[i])cout<<i<<‘ ‘;
   
    cout<<endl;
    system(“pause”);
    return 0;
}
































































另外一种写法:

#include <iostream>

 using namespace std; 

const int MAXN = 1010;

 int map[MAXN][MAXN],bridge[MAXN][MAXN]; 

int color[MAXN],d[MAXN],anc[MAXN],cut[MAXN],visited[MAXN];

 int node_for_tree[MAXN],map_for_tree[MAXN][MAXN];

 int N,M,bridgeN,index; 

void DFS(int child,int father,int deep)

{
//在遍历时father先于child,且两者有连边 int i;

  color[child] = 1;//遍历过可是未找到它所属的强连通分量,标志为灰色 

d[child] = deep;//标志找到该节点时是第几层 

anc[child] = deep;#anc[child]等价low[child];

  int nodeN = 0;//标志该强联通分量的节点个数

 for( i = 1 ; i <= N ; i ++ )

{
//枚举所有节点 

if( map[i][child] && i != father && color[i] == 1 )//i已编历,存在回边 

anc[child] = min(anc[child],d[i]);//保存连通分量里度数最小的 

if( map[i][child] && !color[i] )//有连边且未遍历 

DFS(i,child,deep+1);//递归搜索下一层 

nodeN ++;//将新节点加入连通分量中 

anc[child] = min(anc[child],anc[i]); //如果child是根且不知一个顶点在该连通分量中or child非根且没有回边可到达father的祖先

  if(( child == 1 && nodeN > 1 ) || (child != 1 && anc[i] >= d[child] )) 

cut[child] = 1;//标志为割点 

if( anc[i] > d[child] )//说明只能又child到i了,没有回边 

{

  bridge[child][i] = bridge[i][child] = 1;//标记为桥 bridgeN ++;

}

  }

  }

  color[child] = 2;//遍历完时赋为黑色

void tight(int j)

visited[j] = 1; 

node_for_tree[j] = index; 

int i;

 for( i = 1 ; i <= N ; i ++ ) 

{

  if( !visited[i] && map[j][i]) 

if( !bridge[j][i] )//非桥 tight(i);

  }

 }

  int main()

{

  int i,j,a,b; 

char str[20]; //freopen(“3352.txt”,”r”,stdin); //

while( gets(str) != NULL ) 

while( scanf(“%d %d”,&N,&M) != EOF ) 

{

  //printf(“Output for “); 

//puts(str);

 memset(map,0,sizeof(map)); 

memset(color,0,sizeof(color));

 memset(d,0,sizeof(d));

 memset(anc,0,sizeof(anc)); 

memset(cut,0,sizeof(cut));

  memset(visited,0,sizeof(visited));

 memset(node_for_tree,0,sizeof(node_for_tree));

 memset(map_for_tree,0,sizeof(map_for_tree));

 memset(bridge,0,sizeof(bridge)); 

//scanf(” %d %d”,&N,&M); 

for( i = 0 ; i < M ; i ++ )

  {

  scanf(“%d %d”,&a,&b); 

map[a][b] = 1;//图是双向的 

map[b][a] = 1;

 

//getchar();

 bridgeN = 0; 

DFS(1,1,0);//找出关键边,即桥;为缩图成树做准备 

index = 1;

  bool flag = true;

  while( flag )

  {

  flag = false;

  for( i = 1 ; i <= N ; i ++ )//找到第一个没有访问的顶点 

{

  if( !visited[i] )

  {

  flag = true; break;

 

}

 tight(i);//将i所在的连通分量绑定为树的一个节点 

index ++; 

}

  for( i = 1 ; i <= N ; i ++ ) 

for( j = 1 ; j <= N ; j ++ ) 

if( bridge[i][j] )//找到桥边 

{

  int t1 = node_for_tree[i];//找到桥两个端点在树中的编号 

int t2 = node_for_tree[j];

 map_for_tree[t1][t2] = 1; 

map_for_tree[t2][t1] = 1;

 

int temp,leaf_num = 0; 

for( i = 1 ; i < index ; i ++ )//树节点数总共为index

  {

  temp = 0;

 for( j = 1 ; j < index ; j ++ )

  {

 if( map_for_tree[i][j] && i != j )//有通路且不是环 

temp ++;

  }

  if( temp == 1 )//说明找到叶子节点了,度数为一 

leaf_num ++;

 

printf(“%d\n”,(leaf_num+1)/2); 

//getchar(); 

return 0;

  }

第三种写法:

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/110937.html

(0)
上一篇 2026-01-28 11:15
下一篇 2026-01-28 11:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信