poj 2942 (点双联通+判断二分图)

 

poj 2942 (点双联通+判断二分图)

分类: 强连通—双连通—LCA—2-SAT   80人阅读  评论(0)  收藏  举报
ACM 编程 算法 百度 Tarjan

如何判断一个图中是否存在回路  

2011-12-30 20:22:50|  分类: 算法 |  标签:图中回路问题  bfs  dfs  圈的个数  拓扑排序  |字号 订阅

问题描述:给一个图G=<V,E>,问如何判断这个图中是否存在回路?请给出至少3中方法
分析:
方法1:利用减枝的方法,如果G为有向图:
       1)首先删除入读为0的点,并且将对应的和该点相连的点的入读-1。(可以用一个数组表示节点被删除的状态)
       2)重复过程1),直到没有入读为0的点,如果还有没被删除的节点,则该有向图一定存在回路
      如果G为无向图:
       1)首先删除所有度数<=1的点,然后将与这些点相连的所有点的度数-1,然后将所有度数为1的点加入队列中
       2)对队列中的每个点,重复过程1),如果还有没被删除的节点,那么证明该图一定存在回路。
方法2:(有向图)利用拓扑排序
      1)首先利用DFS进行拓扑排序,最后生成一个拓扑序链表,然后为每个节点设置一个是否被访问过标记,用Visit数组
      2) 遍历这个链表,对每个节点v,设置visit[v]=1,如果判断如果存在与该节点相邻的节点u,使得Visit[u]=1,那么证明存在回边,这图中一定存在圈。

方法3:(无向图而言)利用BFS(利用算法导论上BFS的版本,每个节点有一个color属性,标记节点的颜色:“白”、“灰”、“黑”)
      1)直接利用BFS进行遍历,在判断当前节点的相邻的节点时,附加一个判断:如果这个节点的颜色为“灰色”,则return false
      2)遍历完所有的节点,返回true

方法4:(无向图而言)还是利用BFS,在遍历过程中,为每个节点标记一个深度deep[],如果存在某个节点为v,除了其父节点u外,还存在与v相邻的节点w使得deep[v]<=deep[w]的,那么该图一定存在回路。

方法5:(无向图而言)用BFS或DFS遍历,最后判断对于每一个连通分量当中,如果边数m>=节点个数n,那么改图一定存在回路。因此在DFS或BFS中,我们可以统计每一个连通分量的顶点数目n和边数m,如果m>=n则return false;直到访问完所有的节点,return true.

问题扩展:如何求出一个图中所有简单回路的个数,并打印出这些简单回路?

如何判断一个图是否是二分的?  

2011-12-30 17:40:46|  分类: 算法 |  标签:二分图判定  bfs  |字号 订阅

问题描述:给定一个无向图G=<V,E>,如何能够在O(V+E)时间内判断这个图是否是一个二分图(或者称为2着色)

分析:如果这个无向图没有回路,那么它一定是可以二分的,我们直接就可以利用BFS来为二分图着色(根据当前节点的颜色,对其所有相邻节点涂另一个颜色)。
     如果这个无向图有回路,如果回路的边数是偶偶数,那么它还是二分的,如果是奇数,那么它就不是二分的。如何判断回路的边数是奇数还是偶数呢?
    方法1:利用DFS记录初始节点到每个节点的距离d[],初始节点为s,假设当前访问节点为v,如图:
poj 2942 (点双联通+判断二分图)_第1张图片
 c是从v到s和从u道s的交叉点,c当然可以和s相同,如果当前节点v相邻的节点u已经被标记为灰色(即正在栈中、或递归调用还没返回),那么这个图就是存在环路的,如何判断当前环路的边是否是奇数呢,不是一般性,假设d2>d3,那么我们只需要判断d2-d3+1是否为奇数,如果是奇数,则return false,证明当前图不是二分,如果是偶数则继续遍历,直到所有点均已遍历完毕,则返回true。
d2-d3正是从v到c和从u到c距离的差值,环c->....->v->u->...->c的变长可以表示为N=2*d3+d2-d3+1,所以N的奇偶性完全由d2-d3+1决定。当然在DFS遍历的时候也可能出现如下情形:
poj 2942 (点双联通+判断二分图)_第2张图片
当前访问的节点v,与它相邻的节点u已经是灰色,那么按照刚才的分析,color[u]="Gray",但中不一定构成回路,但我们的判断d2-d1+1依然正确,因为这种情况下d2-d1+1=2,一定是偶数
时间代价O(V+E),空间O(1)(不考虑本身图所占用的结构开销)
 
方法2:我们还可以利用类似减枝的方法来判断图中的回路是否为偶数,基本思想是这样的,如果一个无向图中存在环路,那么在这个环路中每个节点的度数>=2,。
1,首先,我们需要统计每个节点的度数,记录在degree[n]中 O(V+E)
        2,删除所有度数<=1的节点(删除操作就是将该节点对应的边删除,并将对应的顶点的度数-1),然后将所有更新后的顶点度数为1的点加入队列当中。O(V)
       3,然后对队列中的每个元素重复步骤1,最后如果还有没有被删除的节点(即还存在度数>=2的节点),证明图中存在回路。如果全部都已删除则图中不存在回路,即图是二分的
        4,(上面的过程看做预处理过程)如果存在回路,接下来,对剩下的每一个节点,统计从这个节点开始的所有回路(即用DFS从这一点开始,又回到这一点的所有回路),并记录其路径的长度,并标记被访问的状态,如果存在偶数边则return false。
        5,如果存在还没有被访问的点(另一个连通分支的回路),继续步骤4,知道所有节点都被访问完毕,return true.
方法2的复杂度要高一些,应该有一些可以优化的地方。

题意:亚瑟王要在圆桌上召开骑士会议,为了不引发骑士之间的冲突,并且能够让会议的议题有令人满意的结果,每次开会前都必须对出席会议的骑士有   如下要求:1:相互憎恨的两个骑士不能坐在直接相邻的2个位置;2:出席会议的骑士数必须是奇数,这是为了让投票表决议题时都能有结果。

现在给定准备去开会的骑士数n,再给出m对憎恨关系(表示某2个骑士之间是互相憎恨的),问有多少骑士不管跟哪些骑士都无法组成圆桌会议?    

思路:没有矛盾的骑士之间建边,能组成圆桌会议的肯定形成一个环,并且环的点数为奇数。可以用Tarjan求出点双联通分量,判断每个联通分量是否存在  奇环,如果存在奇环的话,该联通分量的所有节点都可以参加会议。一个图是二分图是不存在奇环的充要条件,所以只需要判断该联通分量是否是二分图 即可。







[cpp]  view plain copy
  1. #include<stdio.h>  
  2. #include<stack>  
  3. #include<math.h>  
  4. #include<string.h>  
  5. const int N=1010;  
  6. using namespace std;  
  7. int cnt[N],low[N],dfs[N],head[N],num,ans,idx,first[N],color[N],flag,n,eenum;  
  8. bool map[N][N],vis[N];  
  9. struct edge  
  10. {  
  11.     int st,ed,next;  
  12. }e[N*N],ee[N*N];  
  13. void addedge(int x,int y)  
  14. {  
  15.     e[num].st=x;e[num].ed=y;e[num].next=head[x];head[x]=num++;  
  16.     e[num].st=y;e[num].ed=x;e[num].next=head[y];head[y]=num++;  
  17. }  
  18. void Addedge(int x,int y)  
  19. {  
  20.     ee[eenum].st=x;ee[eenum].ed=y;ee[eenum].next=first[x];first[x]=eenum++;  
  21.     ee[eenum].st=y;ee[eenum].ed=x;ee[eenum].next=first[y];first[y]=eenum++;  
  22. }  
  23. bool Judge(int u,int cor)//判断二分图  
  24. {  
  25.     color[u]=cor;  
  26.     int i,v;  
  27.     for(i=first[u];i!=-1;i=ee[i].next)  
  28.     {  
  29.         v=ee[i].ed;  
  30.         if(color[v]==-1)  
  31.         {  
  32.             return Judge(v,cor^1);  
  33.         }  
  34.         else if(color[v]==color[u])return true;//不是二分图  
  35.     }  
  36.   return false;  
  37. }  
  38. stack<int>Q;  
  39. void Tarjan(int u,int id)  
  40. {  
  41.     int v,i,temp,j;  
  42.     dfs[u]=low[u]=idx++;  
  43.     for(i=head[u];i!=-1;i=e[i].next)  
  44.     {  
  45.         v=e[i].ed;  
  46.         if(i==(id^1))continue;        
  47.         if(dfs[v]==-1)  
  48.         {  
  49.             Q.push(i);    
  50.             Tarjan(v,i);  
  51.             low[u]=low[u]>low[v]?low[v]:low[u];  
  52.             if(dfs[u]<=low[v])//u为割点  
  53.             {  
  54.                 memset(first,-1,sizeof(first));  
  55.                 memset(color,-1,sizeof(color));  
  56.                 eenum=0;  
  57.                 do  
  58.                 {  
  59.                     temp=Q.top();  
  60.                     Q.pop();  
  61.                     Addedge(e[temp].st,e[temp].ed);  
  62.                 }while(temp!=i);//一个双联通分量的所有边  
  63.                 if(Judge(u,0))//如果不是二分图该联通分量里所有点都可以参加会议  
  64.                 {  
  65.                     for(j=0;j<eenum;j+=2)  
  66.                         vis[ee[j].st]=vis[ee[j].ed]=true;  
  67.                 }  
  68.             }  
  69.         }  
  70.         else if(low[u]>dfs[v])  
  71.         {  
  72.             Q.push(i);  
  73.             low[u]=dfs[v];  
  74.         }  
  75.     }  
  76. }  
  77. int main()  
  78. {  
  79.     int m,x,y,i,j,sum,Case=0;  
  80.     while(scanf("%d%d",&n,&m)!=-1&&n+m)  
  81.     {  
  82.         memset(head,-1,sizeof(head));  
  83.         memset(map,false,sizeof(map));  
  84.         num=0;  
  85.         for(i=0;i<m;i++)  
  86.         {  
  87.             scanf("%d%d",&x,&y);  
  88.             map[x][y]=map[y][x]=true;  
  89.         }  
  90.         for(i=1;i<=n;i++)  
  91.         {  
  92.             for(j=i+1;j<=n;j++)  
  93.                 if(map[i][j]==false)  
  94.                     addedge(i,j);  
  95.         }  
  96.         memset(dfs,-1,sizeof(dfs));  
  97.         memset(vis,false,sizeof(vis));  
  98.         ans=idx=0;sum=0;  
  99.         for(i=1;i<=n;i++)//图可能不连通  
  100.         {  
  101.             if(dfs[i]==-1)  
  102.                 Tarjan(i,-1);  
  103.         }  
  104.         for(i=1;i<=n;i++)  
  105.             if(!vis[i])  
  106.                 sum++;  
  107.         printf("%d\n",sum);  
  108.     }  
  109.     return 0;  
  110. }  

你可能感兴趣的:(poj 2942 (点双联通+判断二分图))