目录
前言
图是一种比树复杂的非线性数据结构,甚至可以说是数据结构中最难的部分,把图掌握了数据结构这块就基本拿下了,今天我们主要学习图的连通性。
一、图的基础知识
首先,我们先来了解一下图的一些基本概念。
图是由顶点集合和顶点之间的边集合组成,通常表示为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),如果满足,,则称G1是G的子图。
- 稀疏图和稠密图:有很少条边的图称为稀疏图,反之就是稠密图。
- 权和网:在实际应用中,每条边都可以标上具有某种含义的数值,这些数值称为该边上的权,而像这种带权的图通常称之为网。
- 邻接顶点:如果(V1,V2)是E(G)中的一条边,则称V1,V2互为邻接顶点。
- 通路:从一个顶点(起点)到另一个顶点(终点)的过程中,顶点和边的交替序列称为通路。
- 回路:对于一个通路,如果起点等于终点,那么就称为回路。
- 连通:无向图中,如果两个顶点之间有路径(不一定是直接相连),那么这两个顶点就是连通的。
- 强连通:有向图中,如果两个顶点之间有路径(不一定是直接相连),那么这两个顶点就是强连通的。
- 连通图:如果无向图中任意两个顶点都是连通的,那么该图就是连通图。
- 强连通图:如果有向图中任意两个顶点都是连通的,那么该有向图就是强连通图。
- 连通分量:无向图中的极大连通子图(极大连通子图是指该子图是原无向图的连通子图,且额外加入属于原无向图但不属于该子图的顶点后,该子图不再连通)
- 强连通分量:有向图中的极大连通子图(极大连通子图是指该子图是原有向图的连通子图,且额外加入属于原有向图但不属于该子图的顶点后,该子图不再连通)
二、图的邻接矩阵存储
图的存储主要有邻接矩阵和邻接表两种方式,今天我们用到是邻接矩阵。
邻接矩阵是一种比较直观的存储方式,它利用一个二维数组来表示图的结构(图中各顶点之间的邻接关系),二维数组的行数和列数均等于该图的顶点总数,如果两个顶点邻接(相连)则矩阵对应位置为1,如果两个顶点不邻接(不相连)则矩阵对应位置为0,如下图:
注意:对于有向图,我们是把二维数组的行下标作为边的起点,列下标作为边的终点;而对于无向图,一般是将其作为特殊的有向图处理(任意两个顶点之间都是双向相连的有向图就是无向图)。
三、图的连通性检测
在上面介绍图的基本术语时,我们提到两个顶点连通只是代表它们之间有路可以到达,但不一定是直接相连,因此连通其实可以看作是一种“可达”,如果从一个顶点出发,通过某路径可以到达另一顶点,那么就说这两个顶点是连通的,比如上图中的V1可以通过路径1→2→3到达V3,所以V1和V3是连通的。
那么如何判断一个图是否连通呢?显然,只靠邻接矩阵是肯定不行的,因为邻接矩阵只能表示两个顶点是否直接相连,而无法表示是否连通。不过,我们可以在图邻接矩阵的基础上构造可达矩阵(连通矩阵),进而再判断该图是否连通。
具体的判断方法是:假设图的邻接矩阵为M,M的零次方为单位矩阵(即),要判断该图的连通性,则计算图的可达矩阵,其中n为顶点个数,如果中第行第列的元素为0,则顶点到顶点不连通,从而说明该图不连通。
四、代码实现
首先,我们利用昨天创建的整数矩阵类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(相当于得到了),其行列数与connectivityMatrix的行数相同;第二步,将connectivityMatrix矩阵拷贝到tempMultipliedMatrix(相当于得到了M);第三步,利用for循环进行连加连乘,使得tempConnectivityMatrix=tempConnectivityMatrix+tempMultipliedMatrix,tempMlultipliedMatrix=tempMultipliedMatrix*connectivityMatrix不断迭代(最后会得到);第四步,对所得到的可达矩阵(连通矩阵)进行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
运行结果:
可以看到,运行结果与我们之前的预测保持一致。
总结
今天的重点就是如何通过代码来判断一个图是否连通,单看具体的判断方法不是特别难,但如果要深究此判断方法的数学原理与物理意义,就不再简单了,由于本人还未学到离散数学,所以对于此部分没有深入说明。