无向连通图的割点、桥
泳裤王子原创,转载请注明出处 http://blog.csdn.net/tclh123/article/details/6705392
预备知识:
割点集合
在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个连通块,就称这个点集为割点集合。
割边集合
在一个无向连通图中,如果有一个边集合,删除这个边集合以后,原图变成多个连通块,就称这个点集为割边集合。
连通度
点连通度
一个图的点连通度的定义为,最小割点集合中的顶点数。
边连通度
一个图的边连通度的定义为,最小割边集合中的边数。
双连通图
如果一个无向连通图的点/边连通度大于1,则称该图是点/边双连通的(biconnected),简称双连通或重连通。
求割点与桥
概念:
一个图有割点,当且仅当这个图的点连通度为1,则割点集合的唯一元素被称为割点(cut point),又叫关节点(articulation point)。
一个图有桥,当且仅当这个图的边连通度为1,则割边集合的唯一元素被称为桥(bridge),又叫关节边(articulation edge)。(也有人称为割边….)
求法:
使用dfs(深搜)来求割点和桥。先明确一下几点:
1、 图的dfs相当于是对相应的dfs树的遍历。
2、 无向图的dfs树,无论以哪个点为根都可以遍历完所有的点。
3、 无向图的dfs树,没有横叉边(连接两个子树的边)。
(以下结合BYVoid牛神文)
定义dfn[u]为u在dfs搜索树(以下简称为树)中被遍历到的次序号。
定义low[u]为u或u的子树中能通过非父子边追溯到的最早的节点,即dfn[]最小的节点。则有:
low[u]=Min
{
dfn[u],
dfn[v] ,// (u,v)为后向边(返祖边) , 等价于 dfn[v]<dfn[u]且v不为u的父亲节点
low[v], //(u,v)为树枝边(父子边)
}
所以决定low[u]的关键在于 ,子孙有没有返祖边,返祖边到达的高度是否比dfn[u]小。
//-----------------------------------------------------------------------
①割点u,当且仅当满足(1)或(2)
(1) u为树根,且u有多于一个子树。
(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得dfn[u]<=low[v]。
②桥无向边(u,v),当且仅当(u,v)为树枝边,且满足dfn[u]<low[v]。
注:1、为方便程序编写,我们都采用low[v]来判断u。(理论上low[u]也可以判断割点)
例图:
Dfs树:
伪代码:
Init()
Dfn[~]= invis[~] = 0;
RootChild= 0;
Dfs(u,father)
Dfn[u]= low[u] = ++index;
Invis[u]= true;
Each (u, v)
Ifdfn[v]=0 //(u,v)父子边
Dfs(v,u)
Ifu=Root
RootChild++;
Elseif dfn[u]<=low[v]
u is cut
Ifdfn[u]<low[v]
(u, v) is brige
Low[u] = min(low[u], low[v]);
Elseif v!=father && invis[v]=true //(u,v)返祖边
Low[u]= min(low[u], dfn[v]);
Invis[u]= false;
Return;
示例代码:
- //无向连通图bfs 求 割点、桥
- //中间改了几次,写得较烂
- #include<cstdio>
- #include<cstring>
- #include<iostream>
- using namespace std;
- #define FF(x1, x2) for(int i=x1; i<x2; i++)
- #define MAXN 100
- #define MAXM 100
- int dfn[MAXN], low[MAXN]; //low
- struct edge{int u, v; }a[MAXM];
- int first[MAXN], next[MAXM];
- int n, m;
- int num;
- int cut[MAXN], cn; //割点
- edge brige[MAXM]; int bn;//桥
- int invis[MAXN]; //正在访问
- //也可以统统用颜色标记点,白、灰、黑!!!!!!
- int root;
- int dfs(int u, int father) //把 father入栈,避免走反父子边
- {
- invis[u] = 1;
- int child=0;
- dfn[u] = low[u] = ++num; //++num !!!!!!!!!!!!!!!!!!!①
- for(int e=first[u]; e!=-1; e=next[e])
- {
- int v = a[e].v;
- if(!dfn[v]) //父子边
- {
- dfs(v, u);
- child++;
- if(u!=root && dfn[u]<=low[v]) cut[cn++] = u; //cut //要求不是根结点 !!!!!!!!!③
- if(dfn[u]<low[v]) brige[bn].u=u, brige[bn++].v=v; //brige
- low[u] = low[u]<low[v]? low[u]: low[v];
- }
- else if(v != father && invis[v])//反向边 if(v != father) 由于是无向图!确保不要走反父子边
- {
- low[u] = low[u]<dfn[v]? low[u]: dfn[v];
- }
- }
- invis[u] = 0;
- return child;
- }
- void print()
- {
- cout<<"cut cn="<<cn<<endl;
- FF(0, cn)
- {
- cout<<i<<"\t"<<cut[i]<<endl;
- }
- cout<<"brige bn="<<bn<<endl;
- FF(0, bn)
- {
- cout<<i<<"\t"<<"["<<brige[i].u<<","<<brige[i].v<<"]"<<endl;
- }
- }
- void addedge(int u, int v, int e){ next[e] = first[u]; a[e].u = u; a[e].v = v; first[u] = e; }
- void read_graph() { memset(first, -1, sizeof(first)); cin>>n>>m; FF(0, m){ int u, v; cin>>u>>v; addedge(u, v, i); addedge(v, u, i+m); } }//无向边
- int main()
- {
- cn = bn = 0;
- num=0; //dfs 的访问 num,用来初始化 dfn
- memset(dfn, 0, sizeof(dfn));
- memset(invis, 0, sizeof(invis));
- read_graph();
- /*
- // FF(0, n)
- FF(1, n+1) // 1-index
- {
- if(!dfn[i]) //未访问
- {
- if(dfs(i, i)>1) cut[cn++]=i; // u为树根,且u有多于一个子树。
- }
- }
- */
- // 无向连通图 bfs 指定任意根一点,必定遍历完。 !!!!!!!!与有向图不一样④
- root=1;
- if(dfs(1, 1)>1) cut[cn++]=1; // u为树根,且u有多于一个子树。
- print();
- }
- /* Demo~
- input
- 6 6
- 1 2
- 2 4
- 4 1
- 2 3
- 3 5
- 5 6
- output
- cut cn=3
- 0 5
- 1 3
- 2 2
- brige bn=3
- 0 [5,6]
- 1 [3,5]
- 2 [2,3]
- */