求一个连通图的割点,割点的定义是,如果除去此节点和与其相关的边,图不再连通。
连通图的定义:如果图中任意两点都是连通的,那么图被称作连通图。如果此图是有向图,则称为强连通图(注意:需要双向都有路径)
割点:在无向连通图中,删除一个顶点v及其相连的边后,原图从一个连通分量变成了两个或多个连通分量,则称顶点v为割点,同时也称关节点 (Articulation Point)。多连通图没有割点,若在多连通图上至少删去k个顶点才能破坏图的连通性,则称此图为k连通图。貌似有向连通图没有割点这个说法。
算法分析
- 对根节点v,若其有两棵或两棵以上的dfs子树,则该根结点为割点;
- 对非叶子节点v(非根节点),若其某个子树的节点均没有指向v的祖先节点的回边,则v为割点。
如果v的某一个子树无法回溯到v的祖先,则v是割点,所以在遍历v子树的过程中,v可能会被反复判断为割点,如果仅仅是求割点则需要去除重复记录, 如果是求连通分量则不需要。在遍历完节点v的所有子树及v自己的祖先后就可以求出v的最早祖先,进而结束v的处理,回到v的父节点。
//
输入图的邻接矩阵,节点编号0~n
void doDFS( const vectorint> >& G,
int v,
int currDfs,
bool isRoot,vector<
int>& low,vector<
int>& dfs,vector<
int>& articulation);
void calcArticulation( const vectorint> >& G,vector<
int>& articulation)
{
int v = 0;
int currDfs= 0;
vector< int> low(G.size(),- 1), dfs(G.size(),- 1);
doDFS(G,v,currDfs, true,low,dfs,articulation);
}
// G:邻接矩阵, v当前节点,currdfs当前的dfs编号,isroot指示v是否是dfs树的根节点,
// low记录所有节点的最早祖先,dfs记录所有节点的dfs序号,articulation存放求得的割点
void doDFS( const vectorint> >& G,
int v,
int currDfs,
bool isRoot,vector<
int>& low,vector<
int>& dfs,vector<
int>& articulation)
{
dfs[v] = currDfs;
low[v] = currDfs;
bool isArticulation= false;
int child= 0; // 记录节点v的dfs子树个数
for(size_t i= 0; i < G[v].size(); ++i)
{
if(G[v][i]== 0){ continue;}
int next = i;
if(dfs[next] == - 1){
child++;
doDFS(G,next,currDfs+ 1, false,low,dfs,articulation);
if(low[next] >= dfs[v]){ // 子树没有回溯到v的祖先,v是割点
isArticulation = true;
}
else{ // 子树连通祖先,需要更新low[v]
low[v] = low[next] < low[v] ? low[next]:low[v];
}
}
else{
// 注意判断next小于low[v],因为low[v]可能被循环反复更新
low[v] = dfs[next] < low[v] ? dfs[next] : low[v];
}
}
if(isArticulation== true)
{
// 如果是DFS树根,还需要满足额外条件
if(isRoot== true) {
if(child > 1){
articulation.push_back(v);
}
}
else{
articulation.push_back(v);
}
}
}
static void test( const vectorint> >& G)
{
vector< int> articulation;
calcArticulation(G,articulation);
for(size_t i= 0; i < articulation.size();++i){
printf( " %d, ",articulation[i]);
}
printf( " \n ");
}
// ---- 0
// | / \
// | 1 4
// | / \
// 2 3
static void test1()
{
int t[ 5][ 5]={
{ 0, 1, 1, 0, 1},
{ 1, 0, 1, 1, 0},
{ 1, 1, 0, 0, 0},
{ 0, 1, 0, 0, 0},
{ 1, 0, 0, 0, 0}
};
vectorint> > G(
5);
for( int i= 0; i < 5 ;++i){
G[i].assign(t[i],t[i]+ 5);
}
test(G);
}
// 0
// / \
// 1 4
// / \
// 2 3
static void test2()
{
int t[ 5][ 5]={
{ 0, 1, 0, 0, 1},
{ 1, 0, 1, 1, 0},
{ 0, 1, 0, 0, 0},
{ 0, 1, 0, 0, 0},
{ 1, 0, 0, 0, 0}
};
vectorint> > G(
5);
for( int i= 0; i < 5 ;++i){
G[i].assign(t[i],t[i]+ 5);
}
test(G);
}
// 0
// / \
// 1 4
// / \
// 2 3
// \
// 5
static void test3()
{
int t[ 6][ 6]={
{ 0, 1, 0, 0, 1, 0},
{ 1, 0, 1, 1, 0, 0},
{ 0, 1, 0, 0, 0, 0},
{ 0, 1, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0},
{ 0, 0, 0, 1, 0, 0}
};
vectorint> > G(
6);
for( int i= 0; i < 6 ;++i){
G[i].assign(t[i],t[i]+ 6);
}
test(G);
}
// 0
// / \
// 1----3
// /
// 2
static void test4()
{
int t[ 4][ 4]={
{ 0, 1, 0, 1},
{ 1, 0, 1, 1},
{ 0, 1, 0, 0},
{ 1, 1, 0, 0}
};
vectorint> > G(
4);
for( int i= 0; i < 4 ;++i){
G[i].assign(t[i],t[i]+ 4);
}
test(G);
}
// 0
// /
// 1
// / \
// 2 3
static void test5()
{
int t[ 4][ 4]={
{ 0, 1, 0, 0},
{ 1, 0, 1, 1},
{ 0, 1, 0, 0},
{ 0, 1, 0, 0}
};
vectorint> > G(
4);
for( int i= 0; i < 4 ;++i){
G[i].assign(t[i],t[i]+ 4);
}
test(G);
}
// 双连通图,没有割点
// -----0
// | / |
// | 1 |
// | / \ |
// 2 3
static void test6()
{
int t[ 4][ 4]={
{ 0, 1, 1, 1},
{ 1, 0, 1, 1},
{ 1, 1, 0, 0},
{ 1, 1, 0, 0}
};
vectorint> > G(
4);
for( int i= 0; i < 4 ;++i){
G[i].assign(t[i],t[i]+ 4);
}
test(G);
}
static void test7()
{
int t[ 2][ 2]={
{ 0, 1},
{ 1, 0}
};
vectorint> > G(
2);
for( int i= 0; i < 2 ;++i){
G[i].assign(t[i],t[i]+ 2);
}
test(G);
}
// 0
// / \
// 1 4
// / \
// 2 3
// | \
// ---- 5
static void test8()
{
int t[ 6][ 6]={
{ 0, 1, 0, 0, 1, 0},
{ 1, 0, 1, 1, 0, 1},
{ 0, 1, 0, 0, 0, 0},
{ 0, 1, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0},
{ 0, 1, 0, 1, 0, 0}
};
vectorint> > G(
6);
for( int i= 0; i < 6 ;++i){
G[i].assign(t[i],t[i]+ 6);
}
test(G);
}
void testArticulation()
{
test1();
test2();
test3();
test4();
test5();
test6();
test7();
test8();
}
void doDFS( const vector
void calcArticulation( const vector
{
int v = 0;
int currDfs= 0;
vector< int> low(G.size(),- 1), dfs(G.size(),- 1);
doDFS(G,v,currDfs, true,low,dfs,articulation);
}
// G:邻接矩阵, v当前节点,currdfs当前的dfs编号,isroot指示v是否是dfs树的根节点,
// low记录所有节点的最早祖先,dfs记录所有节点的dfs序号,articulation存放求得的割点
void doDFS( const vector
{
dfs[v] = currDfs;
low[v] = currDfs;
bool isArticulation= false;
int child= 0; // 记录节点v的dfs子树个数
for(size_t i= 0; i < G[v].size(); ++i)
{
if(G[v][i]== 0){ continue;}
int next = i;
if(dfs[next] == - 1){
child++;
doDFS(G,next,currDfs+ 1, false,low,dfs,articulation);
if(low[next] >= dfs[v]){ // 子树没有回溯到v的祖先,v是割点
isArticulation = true;
}
else{ // 子树连通祖先,需要更新low[v]
low[v] = low[next] < low[v] ? low[next]:low[v];
}
}
else{
// 注意判断next小于low[v],因为low[v]可能被循环反复更新
low[v] = dfs[next] < low[v] ? dfs[next] : low[v];
}
}
if(isArticulation== true)
{
// 如果是DFS树根,还需要满足额外条件
if(isRoot== true) {
if(child > 1){
articulation.push_back(v);
}
}
else{
articulation.push_back(v);
}
}
}
static void test( const vector
{
vector< int> articulation;
calcArticulation(G,articulation);
for(size_t i= 0; i < articulation.size();++i){
printf( " %d, ",articulation[i]);
}
printf( " \n ");
}
// ---- 0
// | / \
// | 1 4
// | / \
// 2 3
static void test1()
{
int t[ 5][ 5]={
{ 0, 1, 1, 0, 1},
{ 1, 0, 1, 1, 0},
{ 1, 1, 0, 0, 0},
{ 0, 1, 0, 0, 0},
{ 1, 0, 0, 0, 0}
};
vector
for( int i= 0; i < 5 ;++i){
G[i].assign(t[i],t[i]+ 5);
}
test(G);
}
// 0
// / \
// 1 4
// / \
// 2 3
static void test2()
{
int t[ 5][ 5]={
{ 0, 1, 0, 0, 1},
{ 1, 0, 1, 1, 0},
{ 0, 1, 0, 0, 0},
{ 0, 1, 0, 0, 0},
{ 1, 0, 0, 0, 0}
};
vector
for( int i= 0; i < 5 ;++i){
G[i].assign(t[i],t[i]+ 5);
}
test(G);
}
// 0
// / \
// 1 4
// / \
// 2 3
// \
// 5
static void test3()
{
int t[ 6][ 6]={
{ 0, 1, 0, 0, 1, 0},
{ 1, 0, 1, 1, 0, 0},
{ 0, 1, 0, 0, 0, 0},
{ 0, 1, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0},
{ 0, 0, 0, 1, 0, 0}
};
vector
for( int i= 0; i < 6 ;++i){
G[i].assign(t[i],t[i]+ 6);
}
test(G);
}
// 0
// / \
// 1----3
// /
// 2
static void test4()
{
int t[ 4][ 4]={
{ 0, 1, 0, 1},
{ 1, 0, 1, 1},
{ 0, 1, 0, 0},
{ 1, 1, 0, 0}
};
vector
for( int i= 0; i < 4 ;++i){
G[i].assign(t[i],t[i]+ 4);
}
test(G);
}
// 0
// /
// 1
// / \
// 2 3
static void test5()
{
int t[ 4][ 4]={
{ 0, 1, 0, 0},
{ 1, 0, 1, 1},
{ 0, 1, 0, 0},
{ 0, 1, 0, 0}
};
vector
for( int i= 0; i < 4 ;++i){
G[i].assign(t[i],t[i]+ 4);
}
test(G);
}
// 双连通图,没有割点
// -----0
// | / |
// | 1 |
// | / \ |
// 2 3
static void test6()
{
int t[ 4][ 4]={
{ 0, 1, 1, 1},
{ 1, 0, 1, 1},
{ 1, 1, 0, 0},
{ 1, 1, 0, 0}
};
vector
for( int i= 0; i < 4 ;++i){
G[i].assign(t[i],t[i]+ 4);
}
test(G);
}
static void test7()
{
int t[ 2][ 2]={
{ 0, 1},
{ 1, 0}
};
vector
for( int i= 0; i < 2 ;++i){
G[i].assign(t[i],t[i]+ 2);
}
test(G);
}
// 0
// / \
// 1 4
// / \
// 2 3
// | \
// ---- 5
static void test8()
{
int t[ 6][ 6]={
{ 0, 1, 0, 0, 1, 0},
{ 1, 0, 1, 1, 0, 1},
{ 0, 1, 0, 0, 0, 0},
{ 0, 1, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0},
{ 0, 1, 0, 1, 0, 0}
};
vector
for( int i= 0; i < 6 ;++i){
G[i].assign(t[i],t[i]+ 6);
}
test(G);
}
void testArticulation()
{
test1();
test2();
test3();
test4();
test5();
test6();
test7();
test8();
}