求无向连通图的割点

求一个连通图的割点,割点的定义是,如果除去此节点和与其相关的边,图不再连通。

连通图的定义:如果图中任意两点都是连通的,那么图被称作连通图。如果此图是有向图,则称为强连通图(注意:需要双向都有路径)

割点:在无向连通图中,删除一个顶点v及其相连的边后,原图从一个连通分量变成了两个或多个连通分量,则称顶点v为割点,同时也称关节点 (Articulation Point)。多连通图没有割点,若在多连通图上至少删去k个顶点才能破坏图的连通性,则称此图为k连通图。貌似有向连通图没有割点这个说法。

连通分量:求无向图的连通分量的时候需要先找到割点。在求有向图的强连通分量的时候,所使用的方法与求无向连通图割点的做法很相似,所谓的Tarjan算法。

算法分析

经典的解法是建立DFS搜索树,有两类树节点可以成为割点:

  1. 对根节点v,若其有两棵或两棵以上的dfs子树,则该根结点为割点;
  2. 对非叶子节点v(非根节点),若其某个子树的节点均没有指向v的祖先节点的回边,则v为割点。

实现的时候,随着DFS的进行,需要维护2类信息,一是每个节点v的DFS序号(节点的访问次序),一是每个节点v及v的子树在DFS树中能追溯到的最早祖先节点(即DFS序号最小),将其记为v的最早祖先(至少能追溯到v的父节点)。

如果v的某一个子树无法回溯到v的祖先,则v是割点,所以在遍历v子树的过程中,v可能会被反复判断为割点,如果仅仅是求割点则需要去除重复记录, 如果是求连通分量则不需要。在遍历完节点v的所有子树及v自己的祖先后就可以求出v的最早祖先,进而结束v的处理,回到v的父节点。

//输入图的邻接矩阵,节点编号0~n    
    void doDFS(const vector<vector<int> >& G,int v,int currDfs,bool isRoot,vector<int>& low,vector<int>& dfs,vector<int>& articulation);    
    void calcArticulation(const vector<vector<int> >& 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 vector<vector<int> >& 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 vector<vector<int> >& 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}    
        };    
            
        vector<vector<int> > 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}    
        };    
            
        vector<vector<int> > 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}    
        };    
            
        vector<vector<int> > 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}    
        };    
            
        vector<vector<int> > 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}    
        };    
            
        vector<vector<int> > 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}    
        };    
            
        vector<vector<int> > 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}            
        };    
            
        vector<vector<int> > 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}    
        };    
            
        vector<vector<int> > 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();    
    }

时间: 2024-12-17 07:48:49

求无向连通图的割点的相关文章

无重边无向连通图的割点和桥

1 #include<cstdio> 2 #include<cstring> 3 #include<vector> 4 #include<algorithm> 5 using namespace std; 6 vector<vector<int> > g; 7 int dfn[11000];//节点在dfs过程中的访问序号(也可以叫做开始时间) 8 int low[11000];//节点的子树中能够通过非父子边追溯到的最早的节点的df

Tarjan算法求解无向连通图的割点的模板

#include<cstdio> #include<cstring> #include<cmath> #include<vector> #include<algorithm> using namespace std; const int maxn=1111;//有多少个结点 vector<int>G[maxn]; int visited[maxn];//标记该节点有没有访问过 int node,edge;//顶点数目 int tmpd

ZOJ 2588 Burning Bridges(无向连通图求割边)

题目地址:ZOJ 2588 由于数组开小了而TLE了..这题就是一个求无向连通图最小割边.仅仅要推断dfn[u]是否<low[v],由于low指的当前所能回到的祖先的最小标号,增加low[v]大于dfn[u]时,说明v无法通过其它边回到u之前的点.也就是说v假设想要回到u的祖先点.必需要经过u点,那这条边非常明显就是一条割边.这题还要去重边,假如有重边的话.说明怎么销毁哪条边总能通过还有一条边,所以仅仅要有重边.说明这两点之间没有割边. 代码例如以下: #include <iostream&g

【编程题目】求一个有向连通图的割点,割点的定义是,如果除去此节点和与其相关的边, 有向图不再连通

39.(树.图.算法)(2).求一个有向连通图的割点,割点的定义是,如果除去此节点和与其相关的边,有向图不再连通,描述算法. 思路:这里有个问题,对于图的连通性,我默认它要求强连通.采用了最简单的办法,即每次删掉一条边,判断图还是否连通.若变得不连通了就认为此点是割点. 连通性的判断也采用了直觉上简单的方法,就是对每一个点判断是否有向内指向它的边和它向外指向的边.(question:如此直观的方法是否会有错呢?) /* 39.(树.图.算法) (2). 求一个有向连通图的割点,割点的定义是,如果

设计一个算法,採用BFS方式输出图G中从顶点u到v的最短路径(不带权的无向连通图G採用邻接表存储)

思想:图G是不带权的无向连通图.一条边的长度计为1,因此,求带顶点u和顶点v的最短的路径即求顶点u和顶点v的边数最少的顶点序列.利用广度优先遍历算法,从u出发进行广度遍历,类似于从顶点u出发一层一层地向外扩展,当第一次找到顶点v时队列中便包括了从顶点u到顶点v近期的路径,如图所看到的,再利用队列输出最路径(逆路径),所以设计成非循环队列. 相应算法例如以下: typedef struct  { int data;//顶点编号 int parent;//前一个顶点的位置 } QUEUE;//非循环

设计一个算法,采用BFS方式输出图G中从顶点u到v的最短路径(不带权的无向连通图G采用邻接表存储)

思想:图G是不带权的无向连通图,一条边的长度计为1,因此,求带顶点u和顶点v的最短的路径即求顶点u和顶点v的边数最少的顶点序列.利用广度优先遍历算法,从u出发进行广度遍历,类似于从顶点u出发一层一层地向外扩展,当第一次找到顶点v时队列中便包含了从顶点u到顶点v最近的路径,如图所示,再利用队列输出最路径(逆路径),所以设计成非循环队列. 对应算法如下: typedef struct  { int data; //顶点编号 int parent; //前一个顶点的位置 } QUEUE; //非循环队

【XSY1295】calc n个点n条边无向连通图计数 prufer序列

题目大意 求\(n\)个点\(n\)条边的无向连通图的个数 \(n\leq 5000\) 题解 显然是一个环上有很多外向树. 首先有一个东西:\(n\)个点选\(k\)个点作为树的根的生成森林个数为: \[ \binom{n}{k}\times n^{n-k-1}\times k \] 前面\(\binom{n}{k}\)是这些根的选编号的方案数,后面是prufer序列得到的:前面\(n-k-1\)个数可以是\(1\)~\(n\),第\(n-k\)个数是\(1\)~\(k\). 我的理解是:每个

web-请求无缓存

<head><META HTTP-EQUIV="pragma" CONTENT="no-cache"><META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate"><meta http-equiv="Content-Type" content="text/html; charset

无向连通图图求割点

概念: 割点:在一个无相连通图中,如果删除某个顶点后,图不再连接(即任意两点之间不再相互到达),我们称这样的顶点为割点(或者称为割顶). 思考: 很容易想到的方法是:以此删除每个顶点,然后用深度优先搜索或者广度优先搜索来检查图是否依然连通.如果删除某个顶点后,,导致图不再联通,那么刚才删除的顶点就是割点,但是这种方法复杂度太高,所以需要换一种方法: 首相我们从图中的任意顶点开始对图进行遍历,我们在遍历的时候一定会遇到割点(废话),关键是如何确认一个顶点是割点. 假如我们深度优先遍历事访问到了k点