日撸Java三百行(day32:图的连通性检测)

目录

前言

一、图的基础知识

二、图的邻接矩阵存储

三、图的连通性检测

四、代码实现

总结


前言

图是一种比树复杂的非线性数据结构,甚至可以说是数据结构中最难的部分,把图掌握了数据结构这块就基本拿下了,今天我们主要学习图的连通性。

一、图的基础知识

首先,我们先来了解一下图的一些基本概念。

图是由顶点集合和顶点之间的边集合组成,通常表示为G(V,E),其中G表示图,V是G中顶点的集合,E是G中边的集合。例如:在下图中,有0、1、2、3、4这五个顶点,有0-1、0-2、0-4、1-2、2-3、3-4这六条边,所以该图可以表示为G(V,E),V(G)={V0,V1,V2,V3,V4},E(G)={(V0,V1),(V0,V2),(V0,V4),(V1,V2),(V2,V3),(V3,V4)}。

根据图中边是否有方向,图可以分为无向图和有向图;而根据边长是否有权重,图又可以分为带权图和不带权图,例如:G1是无向图,0和1之间有一条边,0到1和1到0是等价的;G2是有向图,0和1之间只有一条有向边,所以从1可以到0,但是从0不能到1;G3是带权无向图,顶点之间的边加上了权重的度量;G4是带权有向图,这时需要注意两顶点之间不同方向的边的权重是可以不同的,G4图中从3到4的边的权重为60,但是从4到3的边的权重却是70。

注意:无向图和有向图的边的表示是不一样的,无向图表示边用“()”,且交换顶点顺序后边不变;有向图表示边用“<>”,且交换顶点顺序边改变。比如,在下图G1中(V3,V4)和(V4,V3)均表示红色那条边,而在图G2中<V3,V4>表示绿色那条边,<V4,V3>表示橙色那条边。

接着,我们介绍一些有关图的基本术语:

  • 子图:有点类似于字符串中子串的概念,我们假设有两个图G=(V,E),G1=(V1,E1),如果满足V1\subseteq VE1\subseteq E,则称G1是G的子图。
  • 稀疏图和稠密图:有很少条边的图称为稀疏图,反之就是稠密图。
  • 权和网:在实际应用中,每条边都可以标上具有某种含义的数值,这些数值称为该边上的权,而像这种带权的图通常称之为网。
  • 邻接顶点:如果(V1,V2)是E(G)中的一条边,则称V1,V2互为邻接顶点。
  • 通路:从一个顶点(起点)到另一个顶点(终点)的过程中,顶点和边的交替序列称为通路。
  • 回路:对于一个通路,如果起点等于终点,那么就称为回路。
  • 连通:无向图中,如果两个顶点之间有路径(不一定是直接相连),那么这两个顶点就是连通的。
  • 强连通:有向图中,如果两个顶点之间有路径(不一定是直接相连),那么这两个顶点就是强连通的。
  • 连通图:如果无向图中任意两个顶点都是连通的,那么该图就是连通图。
  • 强连通图:如果有向图中任意两个顶点都是连通的,那么该有向图就是强连通图。
  • 连通分量:无向图中的极大连通子图(极大连通子图是指该子图是原无向图的连通子图,且额外加入属于原无向图但不属于该子图的顶点后,该子图不再连通)
  • 强连通分量:有向图中的极大连通子图(极大连通子图是指该子图是原有向图的连通子图,且额外加入属于原有向图但不属于该子图的顶点后,该子图不再连通)

二、图的邻接矩阵存储

图的存储主要有邻接矩阵和邻接表两种方式,今天我们用到是邻接矩阵。

邻接矩阵是一种比较直观的存储方式,它利用一个二维数组来表示图的结构(图中各顶点之间的邻接关系),二维数组的行数和列数均等于该图的顶点总数,如果两个顶点邻接(相连)则矩阵对应位置为1,如果两个顶点不邻接(不相连)则矩阵对应位置为0,如下图:

注意:对于有向图,我们是把二维数组的行下标作为边的起点,列下标作为边的终点;而对于无向图,一般是将其作为特殊的有向图处理(任意两个顶点之间都是双向相连的有向图就是无向图)。

三、图的连通性检测

在上面介绍图的基本术语时,我们提到两个顶点连通只是代表它们之间有路可以到达,但不一定是直接相连,因此连通其实可以看作是一种“可达”,如果从一个顶点出发,通过某路径可以到达另一顶点,那么就说这两个顶点是连通的,比如上图中的V1可以通过路径1→2→3到达V3,所以V1和V3是连通的。

那么如何判断一个图是否连通呢?显然,只靠邻接矩阵是肯定不行的,因为邻接矩阵只能表示两个顶点是否直接相连,而无法表示是否连通。不过,我们可以在图邻接矩阵的基础上构造可达矩阵(连通矩阵),进而再判断该图是否连通。

具体的判断方法是:假设图的邻接矩阵为M,M的零次方为单位矩阵(即M^{0}=I),要判断该图的连通性,则计算图的可达矩阵M^{a}=M^{0}+M^{1}+M^{2}+......+M^{n-1},其中n为顶点个数,如果M^{a}中第i行第j列的元素为0,则顶点i到顶点j不连通,从而说明该图不连通。

四、代码实现

首先,我们利用昨天创建的整数矩阵类IntMatrix完成今日的基本构造部分,如下:

package datastructure.graph;

import matrix.IntMatrix;

/**
 * Directed graph. Note that undirected graphs are a special case of directed
 * graphs.
 *
 *@auther Xin Lin 3101540094@qq.com.
 */

public class Graph {

	/**
	 * The connectivity matrix.
	 */
	IntMatrix connectivityMatrix;

	/**
	 *********************
	 * The first constructor.
	 * 
	 * @param paraNumNodes The number of nodes in the graph.
	 *********************
	 */
	public Graph(int paraNumNodes) {
		connectivityMatrix = new IntMatrix(paraNumNodes, paraNumNodes);
	} // Of the first constructor

	/**
	 *********************
	 * The second constructor.
	 * 
	 * @param paraMatrix The data matrix.
	 *********************
	 */
	public Graph(int[][] paraMatrix) {
		connectivityMatrix = new IntMatrix(paraMatrix);
	} // Of the second constructor

	/**
	 *********************
	 * Overrides the method claimed in Object, the superclass of any class.
	 *********************
	 */
	public String toString() {
		String resultString = "This is the connectivity matrix of the graph.\r\n"
				+ connectivityMatrix;
		return resultString;
	} // Of toString

因为我们用到了昨天创建的IntMatrix类,所以在程序开头不要忘了import;这里定义了一个IntMatrix类的矩阵connectivityMatrix,然后创建了两个构造方法,与昨天的前两个构造方法基本相同;最后,照例重写toString()方法进行遍历。

然后,我们开始创建方法。第一步,创建一个单位矩阵tempConnectivityMatrix(相当于得到了M^{0}),其行列数与connectivityMatrix的行数相同;第二步,将connectivityMatrix矩阵拷贝到tempMultipliedMatrix(相当于得到了M);第三步,利用for循环进行连加连乘,使得tempConnectivityMatrix=tempConnectivityMatrix+tempMultipliedMatrix,tempMlultipliedMatrix=tempMultipliedMatrix*connectivityMatrix不断迭代(最后会得到M^{a});第四步,对所得到的可达矩阵(连通矩阵)进行check,利用一个两层for循环检测连通矩阵中是否存在为0的元素,若存在则说明该图不连通,返回false,同时输出不连通的两个顶点;若不存在则说明该图连通,返回true。

    /**
	 *********************
	 * Get the connectivity of the graph.
	 * 
	 * @throws Exception for internal error.
	 *********************
	 */
	public boolean getConnectivity() throws Exception {
		// Step 1. Initialize accumulated matrix: M_a = I.
		IntMatrix tempConnectivityMatrix = IntMatrix
				.getIdentityMatrix(connectivityMatrix.getData().length);

		// Step 2. Initialize M^1.
		IntMatrix tempMultipliedMatrix = new IntMatrix(connectivityMatrix);

		// Step 3. Determine the actual connectivity.
		for (int i = 0; i < connectivityMatrix.getData().length - 1; i++) {
			// M_a = M_a + M^k
			tempConnectivityMatrix.add(tempMultipliedMatrix);

			// M^k
			tempMultipliedMatrix = IntMatrix.multiply(tempMultipliedMatrix, connectivityMatrix);
		} // Of for i

		// Step 4. Check the connectivity.
		System.out.println("The connectivity matrix is: " + tempConnectivityMatrix);
		int[][] tempData = tempConnectivityMatrix.getData();
		for (int i = 0; i < tempData.length; i++) {
			for (int j = 0; j < tempData.length; j++) {
				if (tempData[i][j] == 0) {
					System.out.println("Node " + i + " cannot reach " + j);
					return false;
				} // Of if
			} // Of for j
		} // Of for i

		return true;
	} // Of getConnectivity

完成方法创建后,我们为其单独写一个测试方法(即单元测试),具体如下:

显然,测试一的图是连通的,测试二的图是不连通的。

    /**
	 *********************
	 * Unit test for getConnectivity.
	 *********************
	 */
	public static void getConnectivityTest() {
		// Test an undirected graph.
		int[][] tempMatrix = { { 0, 1, 0 }, { 1, 0, 1 }, { 0, 1, 0 } };
		Graph tempGraph2 = new Graph(tempMatrix);
		System.out.println(tempGraph2);

		boolean tempConnected = false;
		try {
			tempConnected = tempGraph2.getConnectivity();
		} catch (Exception ee) {
			System.out.println(ee);
		} // Of try

		System.out.println("Is the graph connected? " + tempConnected);

		// Test a directed graph.
		// Remove one arc to form a directed graph.
		tempGraph2.connectivityMatrix.setValue(1, 0, 0);

		tempConnected = false;
		try {
			tempConnected = tempGraph2.getConnectivity();
		} catch (Exception ee) {
			System.out.println(ee);
		} // Of try

		System.out.println("Is the graph connected? " + tempConnected);
	} // Of getConnectivityTest

完整的程序代码,如下:

package datastructure.graph;

import matrix.IntMatrix;

/**
 * Directed graph. Note that undirected graphs are a special case of directed
 * graphs.
 *
 *@auther Xin Lin 3101540094@qq.com.
 */

public class Graph {

	/**
	 * The connectivity matrix.
	 */
	IntMatrix connectivityMatrix;

	/**
	 *********************
	 * The first constructor.
	 * 
	 * @param paraNumNodes The number of nodes in the graph.
	 *********************
	 */
	public Graph(int paraNumNodes) {
		connectivityMatrix = new IntMatrix(paraNumNodes, paraNumNodes);
	} // Of the first constructor

	/**
	 *********************
	 * The second constructor.
	 * 
	 * @param paraMatrix The data matrix.
	 *********************
	 */
	public Graph(int[][] paraMatrix) {
		connectivityMatrix = new IntMatrix(paraMatrix);
	} // Of the second constructor

	/**
	 *********************
	 * Overrides the method claimed in Object, the superclass of any class.
	 *********************
	 */
	public String toString() {
		String resultString = "This is the connectivity matrix of the graph.\r\n"
				+ connectivityMatrix;
		return resultString;
	} // Of toString

	/**
	 *********************
	 * Get the connectivity of the graph.
	 * 
	 * @throws Exception for internal error.
	 *********************
	 */
	public boolean getConnectivity() throws Exception {
		// Step 1. Initialize accumulated matrix: M_a = I.
		IntMatrix tempConnectivityMatrix = IntMatrix
				.getIdentityMatrix(connectivityMatrix.getData().length);

		// Step 2. Initialize M^1.
		IntMatrix tempMultipliedMatrix = new IntMatrix(connectivityMatrix);

		// Step 3. Determine the actual connectivity.
		for (int i = 0; i < connectivityMatrix.getData().length - 1; i++) {
			// M_a = M_a + M^k
			tempConnectivityMatrix.add(tempMultipliedMatrix);

			// M^k
			tempMultipliedMatrix = IntMatrix.multiply(tempMultipliedMatrix, connectivityMatrix);
		} // Of for i

		// Step 4. Check the connectivity.
		System.out.println("The connectivity matrix is: " + tempConnectivityMatrix);
		int[][] tempData = tempConnectivityMatrix.getData();
		for (int i = 0; i < tempData.length; i++) {
			for (int j = 0; j < tempData.length; j++) {
				if (tempData[i][j] == 0) {
					System.out.println("Node " + i + " cannot reach " + j);
					return false;
				} // Of if
			} // Of for j
		} // Of for i

		return true;
	} // Of getConnectivity

	/**
	 *********************
	 * Unit test for getConnectivity.
	 *********************
	 */
	public static void getConnectivityTest() {
		// Test an undirected graph.
		int[][] tempMatrix = { { 0, 1, 0 }, { 1, 0, 1 }, { 0, 1, 0 } };
		Graph tempGraph2 = new Graph(tempMatrix);
		System.out.println(tempGraph2);

		boolean tempConnected = false;
		try {
			tempConnected = tempGraph2.getConnectivity();
		} catch (Exception ee) {
			System.out.println(ee);
		} // Of try

		System.out.println("Is the graph connected? " + tempConnected);

		// Test a directed graph.
		// Remove one arc to form a directed graph.
		tempGraph2.connectivityMatrix.setValue(1, 0, 0);

		tempConnected = false;
		try {
			tempConnected = tempGraph2.getConnectivity();
		} catch (Exception ee) {
			System.out.println(ee);
		} // Of try

		System.out.println("Is the graph connected? " + tempConnected);
	} // Of getConnectivityTest

	/**
	 *********************
	 * The entrance of the program.
	 * 
	 * @param args Not used now.
	 *********************
	 */
	public static void main(String args[]) {
		System.out.println("Hello!");
		Graph tempGraph = new Graph(3);
		System.out.println(tempGraph);

		// Unit test.
		getConnectivityTest();
	} // Of main

} // Of class Graph

运行结果:

e07bccb1f3c845d3a89e01b9945eb598.png

 可以看到,运行结果与我们之前的预测保持一致。

总结

今天的重点就是如何通过代码来判断一个图是否连通,单看具体的判断方法不是特别难,但如果要深究此判断方法的数学原理与物理意义,就不再简单了,由于本人还未学到离散数学,所以对于此部分没有深入说明。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值