割边与割点

1.割边与割点

   (1)割边

                在一个无向连通图中,如果删去其中一条边后,连通块的数量会增多,那么我们称这条边为桥或者是割边.

这里写图片描述如图 A-B为割边

 

    (2)割点

               针对无向连通图,若删除一个点后使得该图不连通,则该点是割点。

                 如图,2便是割点.

 

2.Tarjan算法

      利用tarjan算法可以求出割边与割点.推荐一篇博客:https://www.cnblogs.com/llllllpppppp/p/7593126.html

   (1)割边

      首先我们需要2个数组:

       dfn[i]代表时间戳,是访问该节点的时间。

       low[i]代表追溯值。是该节点以及它的子树通过非搜索树边能追溯的dfn值最小的祖先的dfn值。

       数组low来表示每个点在不经过父节点的前提下,能返回的最早的时间戳。

      我们对图进行搜索时,会形成一颗搜索树。对于一条边(x,y)即一个父节点x,一个子节点y来说,如果dfn[x]<low[y],说明

说明节点y无法追溯到比x更早的节点,即y只能通过x到达更前面的节点。即(x,y)是割边.

代码:

	static void tarjan(int cur, int dad) {// 待判断顶点和它的父节点
		dfn[cur] = low[cur] = ++cnt;
		for (int i = 0; i < list[cur].size(); i++) {
			int v = list[cur].get(i);// v是cur的儿子
			if (dfn[v] == 0) {
				tarjan(v, cur);// 继续遍历
				low[cur] = Math.min(low[cur], low[v]);
				if (dfn[cur] < low[v]) {
					//记录桥的边的两个顶点cur,v  为了方便统计个数,将cur,cur^1记录
					bridge[cur] = bridge[cur^1] = 1;
					System.out.println(cur + "  " +" "+v);//实际割边的两个顶点
				}
			} else if (v != (dad)) {// v是cur的祖先,更新low 因为cur的儿子dfn都为0,不为0说明已经遍历过,是祖先
				low[cur] = Math.min(low[cur], dfn[v]);
			}
		}
	}

   (2)割点

             我们在遍历所有点时会遇到割点。假设访问到了u点,如果在没有访问过的点中,至少有一个点v在不经过u点的情况下,无法回到已访问过的点,则u点是割点。(因为该图删除点u后不连通了)

             如何判断未被访问过的点v在不经过点u的情况下能否返回任何一个已访问过的点。

从树的角度来看,u是v的父亲,v是u的儿子,判断v能否不经过u而回到它的所有祖先。

             枚举u,再枚举跟u有边相连的v,如果存在low[v]>=dfn[u],即返回祖先必须经过u,则u是割点。

代码如下:

	// 顶点cur和父节点dad
	static void tarjan(int cur, int dad) {
		dfn[cur] = low[cur] = ++cnt;
		for (int i = 0; i < list[cur].size(); i++) {
			int v = list[cur].get(i);// v是cur的儿子
			tarjan2(v, cur);
			low[cur] = Math.min(low[cur], low[v]);// 更新low
			// 如果cur是根节点并且子树数目大于等于2,那么也是割点,删除它后两个儿子不连通)
			if (cur == root && list[cur].size() >= 2) {
				flag[cur] = 1;// 标记割点
			} else if (cur != root && low[v] >= dfn[cur]) {// 即儿子v到达祖先必须经过父节点cur
				flag[cur] = 1;
			}
		}
	}

总的代码如下:

package 图论;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

public class 割点与割边 {
	static int n, m, cnt, ks, root;
	static int[] dfn, low, bridge, flag;
	static boolean[] vis;
	static LinkedList<Integer>[] list;

	static void init() {
		list = new LinkedList[n];
		for (int i = 0; i < n; i++) {
			list[i] = new LinkedList<Integer>();
		}
		dfn = new int[2 * n];
		low = new int[2 * n];
		bridge = new int[2 * n];
		vis = new boolean[n];
		flag = new int[n];
	}

	static void tarjan(int cur, int dad) {// 待判断顶点和它的父节点
		dfn[cur] = low[cur] = ++cnt;
		for (int i = 0; i < list[cur].size(); i++) {
			int v = list[cur].get(i);// v是cur的儿子
			if (dfn[v] == 0) {
				tarjan(v, cur);// 继续遍历
				low[cur] = Math.min(low[cur], low[v]);
				if (dfn[cur] < low[v]) {
					// 记录桥的边的两个顶点cur,v 为了方便统计个数,将cur,cur^1记录
					bridge[cur] = bridge[cur ^ 1] = 1;
					System.out.println(cur + "  " + " " + v);// 实际割边的两个顶点
				}
			} else if (v != (dad)) {// v是cur的祖先,更新low 因为cur的儿子dfn都为0,不为0说明已经遍历过,是祖先
				low[cur] = Math.min(low[cur], dfn[v]);
			}
		}
	}

	// 顶点cur和父节点dad
	static void tarjan2(int cur, int dad) {
		dfn[cur] = low[cur] = ++cnt;
		for (int i = 0; i < list[cur].size(); i++) {
			int v = list[cur].get(i);// v是cur的儿子
			if (dfn[v] == 0) {
				tarjan2(v, cur);
				low[cur] = Math.min(low[cur], low[v]);// 更新low
				// 如果cur是根节点并且子树数目大于等于2,那么也是割点,删除它后两个儿子不连通)
				if (dad == -1 && list[cur].size() >= 2) {
					flag[cur] = 0;// 标记割点
				} else if (dad != -1 && low[v] >= dfn[cur]) {// 即儿子v到达祖先必须经过父节点cur
					flag[cur] = 1;
				}
			} else if (v != dad) {// v是cur的祖先,更新low 因为cur的儿子dfn都为0,不为0说明已经遍历过,是祖先
				low[cur] = Math.min(low[cur], dfn[v]);
			}
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		init();
		for (int i = 0; i < m; i++) {
			int u = sc.nextInt() - 1;
			int v = sc.nextInt() - 1;
			list[u].add(v);
			list[v].add(u);
		}
//		tarjan(0, 0);
//
//		for (int i = 0; i < bridge.length; i += 2) {
//			if (bridge[i] != 0) {
//				ks++;
//			}
//		}
//		System.out.println(Arrays.toString(bridge));
//		System.out.println(ks);
//		ks = 0;
		for (int i = 0; i < n; i++) {
			if (dfn[i] == 0) {
				tarjan2(i, -1);
			}
		}
//		tarjan2(0, 0);
		for (int i = 0; i < flag.length; i++) {
			if (flag[i] == 1)
				ks++;
		}
		System.out.println(ks);
	}

}

 

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
拓扑排序、割点割边以及强连通分量是图论中的重要概念和算法。 1. 拓扑排序(Topological Sorting): 拓扑排序是对有向无环图(DAG)进行排序的一种算法。拓扑排序可以得到一个顶点的线性序列,使得对于任意一条有向边(u, v),在序列中顶点u都排在顶点v的前面。拓扑排序常用于表示任务之间的依赖关系,例如在工程项目中确定任务的执行顺序。 2. 割点割边(Cut Vertex and Cut Edge): 割点是指在无向连通图中,如果移除该顶点以及与该顶点相连的所有边,会导致图不再连通,则该顶点被称为割点割边是指在无向连通图中,如果移除该边,会导致图不再连通,则该边被称为割边割点割边的存在可以影响到图的连通性,因此在网络设计、通信等领域有着重要的应用。 3. 强连通分量(Strongly Connected Component): 强连通分量是指在有向图中,如果对于图中任意两个顶点u和v,存在从u到v和从v到u的路径,那么称u和v在同一个强连通分量中。强连通分量可以将有向图的顶点划分成若干个子集,每个子集内的顶点之间互相可达。强连通分量可以用于分析网络中的关键节点,寻找网络的可靠性,以及在编译器设计中进行代码优化等领域。 这些概念和算法在图论中都有着广泛的应用,并且还有许多相关的算法和扩展。深入理解和掌握这些概念和算法,可以帮助我们更好地理解和解决各种与图相关的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值