图的割点和割边

图的割点和割边

首先得弄清楚割点的概念,割点是什么?在一个无向连通图中,如果删除某个顶点后,图不在连通(即任意两点之间不能互相到达),我们称这样的点为割点,而割点如何求呢?
假如我们在深度优先遍历时访问到了k点,这时图就被分割成两部分。一部分是被访问过的点,另一部分则是没有被访问过的点。如何k是割点,那么剩下的没有被访问过的点至少有一个在不经过k点的情况下,是无论如何都再也回不到已访问的点了。那么这个连通图就被分割成多个不连通的子图了。
连通图如下所示:
在这里插入图片描述
还要弄清楚一个概念,时间戳。表示这个顶点在遍历时第几个被访问到的。我们用数组num来记录每个顶点的时间戳。用low数组来记录每个顶点能回到的最小时间戳。
这个算法的关键是在于:当深度优先遍历到顶点u时,假设图中还有顶点v是没有被访问到的点,如何判断顶点v在不经过u的情况下还能回到之前从访问过的点。如果从生成树的角度来说,顶点u就是顶点v的父节点,顶点v是顶点u的儿子,而之前访问过的顶点就是祖先。这里是对顶点v再进行一次深度优先遍历,但是此次不允许通过顶点u,看看是否能回到祖先。不能回到则是割点。
核心代码:

//割点核心算法 
void dfs(int cur,int father)
{
 index ++; //时间戳加1 
 num[cur] = index;
 low[cur] = index;
   for(int i = 1; i <= n; i ++)
   {
	    if(e[cur][i] == 1) //顶点未被访问过 
	    {
		 if(num[i] == 0)
		{
		     child ++;
		     dfs(i,cur); //继续往下深度优先遍历 
		     low[cur] = min(low[cur],low[i]); //更新当前顶点能访问到的最小时间戳 
		    //当前顶点cur不是根节点并且满足low[i]>=num[cur],当前顶点为割点 
		    if(cur != 1 && low[i] >= num[cur])
		    {
		       flag[cur] = 1; 
		   }
		   //当前顶点为割点,并且只有两个儿子 
		   if(cur == 1 && child == 2)
		   {
		      flag[cur] = 1;
		   }
		   
	       }else
	    //否则曾经被访问过,更新cur能访问到的最小时间戳 
	    if(i != father)
	    {
	     low[cur] = min(low[cur],num[i]);
	    }
	 }
   }
   
}

全部代码:

#include<iostream>
#define maxPath 99999999
using namespace std;
int n,m; 
int e[1001][1001];
int num[1001]; //记录顶点访问时间戳
int low[1001]; //记录顶点能回到的最小时间戳 
int index,child; //记录时间 
int flag[1001];
int min(int a,int b)
{
  return a > b? b : a;
}
//割点核心算法 
void dfs(int cur,int father)
{
 index ++; //时间戳加1 
 num[cur] = index;
 low[cur] = index;
   for(int i = 1; i <= n; i ++)
   {
	    if(e[cur][i] == 1) //顶点未被访问过 
	    {
	         if(num[i] == 0)
	          {
	           child ++;
	           dfs(i,cur); //继续往下深度优先遍历 
	           low[cur] = min(low[cur],low[i]); //更新当前顶点能访问到的最小时间戳 
	            //当前顶点cur不是根节点并且满足low[i]>=num[cur],当前顶点为割点 
	            if(cur != 1 && low[i] >= num[cur])
	             {
	               flag[cur] = 1; 
	             }
		    //当前顶点为割点,并且只有两个儿子 
		    if(cur == 1 && child == 2)
		    {
		     flag[cur] = 1;
		    }
		   
	     }else
		    //否则曾经被访问过,更新cur能访问到的最小时间戳 
	    if(i != father)
	    {
	     low[cur] = min(low[cur],num[i]);
	    }
	 }
   }
   
}
int main()
{
   cin >> n >> m;
   for(int i = 1; i <= n; i ++)
   for(int j = 1; j <= n; j ++) 
   if(i == j)
   e[i][j] = 0;
   else
   e[i][j] = maxPath;
   
     
   for(int i = 1; i <= m; i ++)
   {
     int x,y;
     scanf("%d%d",&x,&y);
     e[x][y] = 1; 
     e[y][x] = 1;
   }
   dfs(1,1);
   for(int i = 1; i <= n; i ++)
   {
      if(flag[i] == 1)
      cout << i << " ";
   }
   return 0;
}

割边与割点差不多,只需将low[v] >= num[u]代表的是不可能在不经过父亲节点u而回到祖先的,而low[v] > num[u]则是连父亲都回不到了。
核心代码:

//割边核心算法 
void dfs(int cur,int father)
{
 index ++; //时间戳加1 
 num[cur] = index;
 low[cur] = index;
   for(int i = 1; i <= n; i ++)
   {
    if(e[cur][i] == 1) //顶点未被访问过 
    {
       if(num[i] == 0)
         {
     dfs(i,cur); //继续往下深度优先遍历 
            low[cur] = min(low[cur],low[i]); //更新当前顶点能访问到的最小时间戳 
            //当前顶点cur不是根节点并且满足low[i]>=num[cur],当前顶点为割点 
  if(low[i] > num[cur])
            {
               printf("%d-%d\n",cur,i); 
            }
    }else
    //否则曾经被访问过,更新cur能访问到的最小时间戳 
    if(i != father)
    {
     low[cur] = min(low[cur],num[i]);
    }
 }
   }
   
}

补充一下,割点和割边算法是由Robert E.Tarjan 发明的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值