割点

来源:互联网 发布:淘宝 童鞋 童装 编辑:程序博客网 时间:2024/05/01 20:04
 

割点

分类: 算法 304人阅读 评论(1) 收藏 举报
structstring算法null存储

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

分析:

1. 最简单也是最直接的算法是,删除一个点然后判断连通性,如果删除此点,图不再连通,则此点是割点,反之不是割点(图的连通性一般通过深搜来判定,是否能一次搜索完 全部顶点);

2. 通过深搜优先生成树来判定。从任一点出发深度优先遍历得到优先生成树,对于树中任一顶点V而言,其孩子节点为邻接点。由深度优先生成树可得出两类割点的特性:

     (1)若生成树的根有两棵或两棵以上的子树,则此根顶点必为割点。因为图中不存在连接不同子树顶点的边,若删除此节点,则树便成为森林;

     (2)若生成树中某个非叶子顶点V,其某棵子树的根和子树中的其他节点均没有指向V的祖先的回边,则V为割点。因为删去v,则其子树和图的其它部分被分割开来。

仍然利用深搜算法,只不过在这里定义visited[v]表示为深度优先搜索遍历图时访问顶点v的次序号,定义low[v]=Min{visited[v],low[w],visited[k]},其中w是顶点v在深度优先生成树上的孩子节点;k是顶点v在深度优先生成树上由回边联结的祖先节点。

   割点判定条件:如果对于某个顶点v,存在孩子节点w且low[w]>=visited[v],则该顶点v必为关节点。因为当w是v的孩子节点时,low[w]>=visited[v],表明w及其子孙均无指向v的祖先的回边,那么当删除顶点v后,v的孩子节点将于其他节点被分割开来,从来形成新的连通分量。

[cpp] view plaincopy
  1. #include <iostream>  
  2. #include <string>  
  3. using namespace std;  
  4.   
  5. #define MAX_VERTEX_NUM 13  
  6.   
  7. //邻接表存储结构  
  8. typedef struct ArcNode{  
  9.     int adjvex;  
  10.     ArcNode *nextarc;  
  11. }ArcNode;  
  12.   
  13. typedef struct VNode{  
  14.     string data;  
  15.     ArcNode* firstarc;  
  16. }VNode,AdjList[MAX_VERTEX_NUM];  
  17.   
  18. typedef struct{  
  19.     AdjList vertices;  
  20.     int vexnum, arcnum;  
  21. }ALGraph;  
  22.   
  23. //返回u在图中的位置  
  24. int LocateVex(ALGraph G, string u)  
  25. {  
  26.     for(int i=0; i<G.vexnum; i++)  
  27.         if(G.vertices[i].data==u)  
  28.             return i;  
  29.     return -1;  
  30. }  
  31.   
  32. //构造图  
  33. void CreateDG(ALGraph &G)  
  34. {  
  35.     string v1, v2;  
  36.     int i, j, k;  
  37.     cout<<"请输入顶点数和边数:";  
  38.     cin>>G.vexnum>>G.arcnum;  
  39.   
  40.     cout<<"请输入顶点:";  
  41.     for(i=0; i<G.vexnum; i++)  
  42.     {  
  43.         cin>>G.vertices[i].data;  
  44.         G.vertices[i].firstarc=NULL;  
  45.     }  
  46.   
  47.     cout<<"请输入边:"<<endl;  
  48.     for(k=0; k<G.arcnum; k++)  
  49.     {  
  50.         cin>>v1>>v2;  
  51.         i=LocateVex(G, v1);  
  52.         j=LocateVex(G, v2);  
  53.   
  54.         //无向图  
  55.         ArcNode *arc=new ArcNode;  
  56.         arc->adjvex=j;  
  57.         arc->nextarc=G.vertices[i].firstarc;  
  58.         G.vertices[i].firstarc=arc;  
  59.   
  60.         arc=new ArcNode;  
  61.         arc->adjvex=i;  
  62.         arc->nextarc=G.vertices[j].firstarc;  
  63.         G.vertices[j].firstarc=arc;  
  64.     }  
  65.   
  66. }  
  67.   
  68. //求割点  
  69. int count ;  
  70. int visited[MAX_VERTEX_NUM];  
  71. int low[MAX_VERTEX_NUM];  
  72.   
  73. //从第v0个顶点出发深搜,查找并输出关节点(割点)  
  74. void DFSArticul(ALGraph G, int v0)  
  75. {  
  76.     int min, w;  
  77.     ArcNode *p;  
  78.     visited[v0]=min=++count;//v0是第count个访问的顶点,min的初值为visited[v0],即v0的访问次序  
  79.   
  80.     for(p=G.vertices[v0].firstarc; p ; p=p->nextarc)  
  81.     {  
  82.         w=p->adjvex;  
  83.         if(visited[w]==0)//w未曾访问,是v0的孩子  
  84.         {  
  85.             DFSArticul(G, w);//从第w个顶点出发深搜,查找并输出关节点(割点),返回前求得low[w]  
  86.             if(low[w]<min)//如果v0的孩子节点w的low[]小,说明孩子节点还与其他节点(祖先)相邻  
  87.                 min=low[w];  
  88.             if(low[w]>=visited[v0])//v0的孩子节点w只与v0相连,则v0是关节点(割点)  
  89.                 cout<<G.vertices[v0].data<<" ";  
  90.         }  
  91.         else if(visited[w]<min)//w已访问,则w是v0生成树上祖先,它的访问顺序必小于min  
  92.             min=visited[w];  
  93.     }  
  94.   
  95.     low[v0]=min;//low[v0]取三者最小值  
  96.       
  97. }  
  98.   
  99. void FindArticul(ALGraph G)  
  100. {  
  101.     int i, v;  
  102.     ArcNode *p;  
  103.     count=1;  
  104.     visited[0]=1;//从0号节点开始  
  105.     for(i=1; i<G.vexnum; i++)  
  106.         visited[i]=0;  
  107.     p=G.vertices[0].firstarc;  
  108.     v=p->adjvex;  
  109.     DFSArticul(G, v);  
  110.     if(count<G.vexnum)  
  111.     {  
  112.         cout<<G.vertices[0].data<<" ";  
  113.         while(p->nextarc)  
  114.         {  
  115.             p=p->nextarc;  
  116.             v=p->adjvex;  
  117.             if(visited[v]==0)  
  118.                 DFSArticul(G, v);  
  119.         }  
  120.     }  
  121. }  
  122.   
  123. void main()  
  124. {  
  125.     ALGraph g;  
  126.     CreateDG(g);  
  127.   
  128.     cout<<"割点如下: "<<endl;  
  129.     FindArticul(g);  
  130.     cout<<endl;  
  131. }  



这篇博客讲解的更为详细:http://blog.csdn.net/xinghongduo/article/details/6202646

黑书上给出了关于求点割集的算法,但是比较模糊,我查阅了网络上的相关资料,理解了求点割集的过程,写出如下求点割集的代码,并写了一些简单的证明.

 

割点集的定义:如果在连通图G中去掉某一点后图不连通,那么这个点即为G的割点,所有割点的集合即为点割集。

 

求点割集的方法:利用tarjan算法的思想,用数组dfn[v]存储DFS遍历到点v的时间,数组low[v]存储点v能追溯到最早的祖先节点。

如果对于点v来说有如下结论:

 

 

1.如果点v是DFS序列的根节点,则如果v有一个以上的孩子,则v是一个割点。

2.如果v不是DFS序列根节点,并且点v的任意后继u能追溯到最早的祖先节点low[u]>=dfn[v],则v是一个割点。

 

 

证明1:

  

       假设DFS遍历的第一个节点v不是割点,那么则有low[v]=dfn[v]=1,继续对v的孩子节点u遍历,必然有low[u]>=dfn[v],按照第二条性质,则v是割点,但我们已经假设v不是割点。这是由于v是DFS遍历的起始节点,在遍历序列中v没有祖先节点,v的所有后继节点能追溯到最早的祖先节点最多也就是v了,不可能比v再早了,因此必须把DFS遍历的第一个节点v单独考虑,那么怎么判断v是不是割点呢?

      例如上图,设v是DFS序列访问的第一个节点,对v的孩子节点u和u的所有孩子节点进行DFS遍历并标记为已经访问后,如果v的另一个孩子节点k没有被标记为已经访问,那么u和k之间一定不存在边,也就是说u和k之间的连通必然需要点v,因此如果v是DFS遍历的第一个节点,对v是否为割点的判断方法是:看v是不是有多个孩子,如果有则v是割点。

证明2:

      如果v不是DFS遍历的第一个节点,那么对于v的所有后继节点来说,如果v不是割点(也就是如果删掉点v,剩下的图还是连通图),那么v的后继节点必然能追溯到DFS遍历序列中v的祖先节点,也就是v的后继节点中存在到达DFS序列中v的祖先的路径,因此当DFS回溯到v节点时对于v的所有后继节点u来说,都有low[u]<dfn[v]。

      如果v是一个割点,对所有v的后继节点u进行DFS后,必然有low[u]>=dfn[v],这是因为,当遍历v并将其锁定后,到达v的祖先节点的路径已经被封死,v的后继节点必然不可能访问到v的祖先节点,因此,必然有low[u]>=dfn[v]。

 

 

有了上面的分析,下面写出求无向图点割集的代码:

[cpp] view plaincopy
  1. #include<iostream>  
  2. using namespace std;  
  3.   
  4. struct L  
  5. {  
  6.     int v;  L *next;  
  7. };  
  8.   
  9. class HEAD  
  10. {  
  11. public:  
  12.     int id; L *next;  
  13.     HEAD(){ id=0;   next=NULL;}  
  14. };  
  15.   
  16.   
  17. HEAD head[1000];  int dfn[1000],low[1000],t;     bool lock[1000],C[1000];  
  18.   
  19.   
  20. void find(int father,int v)  
  21. {  
  22.     int count=0;            /*统计v的孩子数*/  
  23.     dfn[v]=low[v]=++t;     /*将访问时间赋给dfn[v]和low[v]*/  
  24.     lock[v]=false;      /*标记v点已经访问过,不能再被访问*/  
  25.     for(L *p=head[v].next;p!=NULL;p=p->next)  
  26.     {  
  27.         if(lock[p->v])  /*如果v的直接后继节点没有访问过,则对其遍历*/  
  28.         {  
  29.             find(v,p->v);  /*对v的直接后继遍历*/  
  30.             count++;        /* 孩子数+1 */  
  31.             if(low[v]>low[p->v])  /*如果v的孩子能追溯到更早的祖先,则v也能追溯到*/  
  32.                 low[v]=low[p->v];  
  33.         }  
  34.         else if(p->v!=father&&low[p->v]<low[v])  /*如果v的直接孩子节点已经被访问过*/  
  35.             low[v]=low[p->v];  
  36.   
  37.         if(!father&&count>1)     /*如果当前节点是DFS遍历到的第一个节点,则判断其是否有多个孩子*/  
  38.             C[v]=true;  
  39.         else if(father&&dfn[v]<=low[p->v])   /*否则判断其后继能否追溯到v的祖先*/  
  40.             C[v]=true;  
  41.     }  
  42. }  
  43.   
  44.   
  45. int main()  
  46. {  
  47.     int n,i,a,b;  
  48.     cin>>n;  
  49.       
  50.     while(cin>>a>>b&&a&&b)      /*建立邻接表,输入无向图边每条a b,以0 0结束*/  
  51.     {  
  52.         L *p=new L;  
  53.         p->next=head[b].next;  
  54.         head[b].next=p;  
  55.         p->v=a;  
  56.         p=new L;  
  57.         p->next=head[a].next;  
  58.         head[a].next=p;  
  59.         p->v=b;  
  60.         head[b].id++;  
  61.         head[a].id++;  
  62.     }  
  63.     memset(lock,true,sizeof(lock));  
  64.     memset(dfn,0,sizeof(int)*1000);  
  65.     memset(C,0,sizeof(C));  /*C数组用来标记那些点是割点,刚开始全部置为false*/  
  66.     t=0;   /*访问时间*/  
  67.     find(0,1);/*开始对1号点DFS,第一个遍历的前驱节点设为0*/  
  68.     for(i=1;i<=n;i++)        /*输入割点*/  
  69.         if(C[i])  
  70.             cout<<i<<' ';  
  71.         cout<<endl;  
  72.           
  73. }