图的割点和割边

在一个无向连通图中,如果删除后某个顶点之后,图不再连通(即任意两点之间不能相互可达),这样的顶点叫做割点。

由此,删去某个顶点之后,然后进行一次dfs搜索一遍判断图是否还连通,便能求出该点是不是割点。

但是时间复杂度颇高。


所以,基于dfs搜索树设计了求割点的算法。

求割点可分为两种情况,

1. 求根节点的是否为割点,只要其有两棵或两棵以上子树,则为割点。

2. 叶节点都不是割点,所以只剩下要求非叶节点,若一非叶节点的子树的节点没有指向该点的祖先节点的回边,说明删除该点之后,该点的祖先节点与该点的子树不再连通,则说明该节点为割点。


使用dfs进行一次搜索,假设搜索结果是下图这样的,即1->3->2->4->5->6


圆圈内代表节点编号,旁边的数值代表该顶点在进行遍历时是第几个被访问到的,我们称做时间戳,用num数组来进行存储每个顶点的时间戳。

当遍历至某个顶点时,判断剩余未访问的点在不经过该点的情况下能否回到在访问该点之前的任意一个点,可以形象地说成该点的儿子在不经过它这个父亲的情况下是否还能回到它的祖先。

所以我们在用一个low数组来记录每个顶点在不经过其父节点时,能够直接回到的最小 '时间戳',如图


所以,对于某点u若至少存在一个儿子顶点v,使low[v] >= num[u],即不能回到祖先,则说明该点u为割点。


代码如下,

#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
struct node{
	int v, next;
}edge[4005];
int head[1005], num[1005], low[1005], ans[1005];
int n, m, no, index, root;
void add(int u, int v){
	edge[no].v = v;
	edge[no].next = head[u];
	head[u] = no++;
}
void init(){
	no = 1, index = 0, root = 1;
	memset(head, -1, sizeof head);
	memset(ans, 0, sizeof ans);
	memset(num, 0, sizeof num);
}
void dfs(int cur, int father){
	int child = 0;
	++index;
	num[cur] = index;
	low[cur] = index;
	int k = head[cur];
	while(k != -1){
		if(num[edge[k].v] == 0){
			++child;
			dfs(edge[k].v, cur);
			low[cur] = min(low[cur], low[edge[k].v]);
			if(cur != root && low[edge[k].v] >= num[cur]) ans[cur] = 1;
			if(child == 2 && cur == root) ans[cur] = 1;
		}
		else if(edge[k].v != father){
			low[cur] = min(low[cur], num[edge[k].v]);
		}
		k = edge[k].next;
	}
}
int main(){
	int i, u, v;
	scanf("%d %d", &n, &m);
	init();
	for(i = 1; i <= m; ++i){
		scanf("%d %d", &u, &v);
		add(u, v);
		add(v, u);
	}
	dfs(root, root);
	for(i = 1; i <= n; ++i)  
        if(ans[i]) printf("%d ", i);  
    printf("\n"); 
	return 0;
}


求割边其实就需要将上面的代码稍加改动,将low[v] >= num[u]改为low[v] > num[u],因为这句代码代表该点的子节点在不经过其父亲节点回不到该点及该点的祖先的,所以u->v这条边是一条割边。

代码如下,

#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
struct node{
	int v, next;
}edge[4005];
int head[1005], num[1005], low[1005], ans[1005];
int n, m, no, index, root;
void add(int u, int v){
	edge[no].v = v;
	edge[no].next = head[u];
	head[u] = no++;
}
void init(){
	no = 1, index = 0, root = 1;
	memset(head, -1, sizeof head);
	memset(ans, 0, sizeof ans);
	memset(num, 0, sizeof num);
}
void dfs(int cur, int father){
	++index;
	num[cur] = index;
	low[cur] = index;
	int k = head[cur];
	while(k != -1){
		if(num[edge[k].v] == 0){
			dfs(edge[k].v, cur);
			low[cur] = min(low[cur], low[edge[k].v]);
			if(low[edge[k].v] > num[cur])
				printf("%d--%d\n", cur, edge[k].v);
		}
		else if(edge[k].v != father){
			low[cur] = min(low[cur], num[edge[k].v]);
		}
		k = edge[k].next;
	}
}
int main(){
	int i, u, v;
	scanf("%d %d", &n, &m);
	init();
	for(i = 1; i <= m; ++i){
		scanf("%d %d", &u, &v);
		add(u, v);
		add(v, u);
	}
	dfs(root, root);
	return 0;
}

时间复杂度为O(M+N),继续加油~


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值