无向图连通性判断的五种方法(BFS、DFS、Union-find、Warshell、Tarjan)

目录

无向连通图的相关定义

主要算法流程

 DFS判断:

BFS判断:

Warshell判断:

Union-Find判断:

Tarjan判断:


无向连通图的相关定义

  1. 连通性:在图G中,两个不同的结点u和结点v之间若存在一条路,则称结点u和结点v是连通的。
  2. 无向连通图:若在无向图G中,任意两点都是连通的,则称图G为连通图。

主要算法流程

 DFS判断:

  • 从任一结点开始,进行一次深度优先遍历。深度优先遍历的结果是一个图的连通分量。当一次遍历没有访问到所有结点,那么就说明图是不连通。
  • 算法步骤及相关分析

a)算法步骤:
    从顶点v出发,访问顶点v,并令visited[v] = 1;
    依次查找v的所有邻接点w,若visited[w] 为0,则从w出发,深度优先遍历图。
    进行判断,遍历visited数组,若visited数组中有一个值不为1,则说明该点未被访问,图不连通。
b)时间复杂度::算法运行时间主要是耗费在寻找邻接w处,寻找一个符合条件的w的时间复杂度是O(V),V个节点就是O(V^2),尽管可能不会寻找V次。
c)空间复杂度:空间复杂度仅耗费在辅助数组visited[]上,空间复杂度为O(V)。
d)改进:
    设置全局静态变量count,记录访问结点的数量,所以判断时不必遍历visited数组,只要判断count值是        否等于结点数量V即可;
    visited数组设置为全局变量,与BFS函数共享。
  • 代码:
//DFS递归
public void  DFS(int[] visited, int v) {
    visited[v] = 1;
	judgeDFSCount ++;
	for(int i=0; i

BFS判断:

  • 从一个结点开始,访问与其关联的所有结点,将这些结点入队,重复此过程,直至队列为空。访问的结点构成一个连通分量,若该连通分量未包含所有结点,则无向图不连通。

  • 算法步骤及相关分析:

a)算法步骤:
    从顶点v开始,访问v并置visited[v] = 1,将v入队;
    只要队列不空,就重复一下操作:
    队首顶点v出队;
    依次查找v所有邻接点w,如果visited[w]值为0,则访问w并置visited[w] = 1,将w放入队列中;
    进行判断,遍历visited数组,若有一个值不为1,则图就不连通。
b)时间复杂度:BFS的时间复杂度主要是耗费在搜索邻接点上,与DFS类型,复杂度也是O(V^2)。
c)空间复杂度:使用一个队列以及辅助数组visited[],空间复杂度也是O(V)。
  • 代码:

此处Queue类为自写的,可简单实现。

//BFS判断
public boolean BFS() {
	int count = 0 ;							//由于BFS不用递归,所以定义局部变量
	boolean flag = false;
	Queue Q = new Queue();				    //使用队列进行BFS
	visited = new int[this.vertexNum];		//记录结点被访问情况
	Q.add(0);								//首先访问0号结点
	while(!Q.isEmpty()) {       			//队列未空进行如下操作
		int s = Q.front();					//首先队首出列,并获取队首元素
		Q.remove();
		visited[s] = 1;						//队首被访问 
		count ++;							//更新count值
		for(int j=0; j

Warshell判断:

  • 图中点与点之间构成边,也可以理解为存在边的两个结点之间存在关系,那么就可以使用Warshell算法求这些关系的传递闭包。Warshell算法更改矩阵的依据实质上是关系的继承,若结点i与结点j之间有边,那么结点 i 就继承结点 j 的连通关系。

  • 主要算法步骤及相关分析:

a)算法步骤:
    复制邻接矩阵给矩阵A,即令A = M ;
    置变量i = 1;
    对所有j如果A[j,i] = 1,则k = 1,2,3,…,n
        A[j,k] = A[j,k] + A[i,k];
    i加1;
    如果i <= n,则转步骤(3),否则进入下一步判断;
    判断矩阵A,A中所有值都为1则无向图连通,否则不连通。
b)时间复杂度:代码的主要时间开销在矩阵的计算上,复杂度为O(V^3)。
c)空间复杂度:空间开销在于需要一个邻接矩阵的复制矩阵,复杂度为O(V^2)。
  • 代码:

由于Java不支持整型的逻辑运算,所以针对上述算法步骤中的步骤3,需要构造一个与邻接矩阵(0-1)对应的Boolean型数组。

或者步骤三的逻辑或直接化成整型的“+”,最后仍是判断矩阵是否存在0。

//Warshell算法判断可达性矩阵
public boolean Warshell() {
	int count = 0;							//计算关键步运行次数以判断时间复杂度
	boolean flag = true;
	boolean[][] tmp = new boolean[this.vertexNum][this.vertexNum];
	for(int i=0; i改进尝试:

其实这个改进效果并不好,而且其本事仍存在问题。解决简单问题没问题,但是图稍微复杂就会出现问题,若有大神有解决方案,请务必指教。

改进效果,通过count变量的大小来体现,与上面的Warshell的count相比,有一定改进。

​
由于无向图邻接矩阵的对称性,所以邻接矩阵的一个半角矩阵就包含了结点之间的关系。所以针对Warshell算法,我进行了相关修改,使其只求解一个半角矩阵。
    修改关系的继承规则:若结点i与结点j右边,那么i继承j的连通关系,并将i的连通关系同赋给j。
    改进关键步骤:
        对所有j,j∈[1,i],i∈[2,n],如果A[i,j] = 1,则对k∈[1,i]
            // A[i,k] = A[i,k] + A[j,k];  		//承载j列关系
            // A[i,k] = A[i,k] + A[k,j];			//承载j行关系
            A[i,k] = A[i,k] + A[j,k] + A[k.j]; 	//合并以上两行
            A[j,k] = A[j,k] + A[i,k];			//j与i的关系保持一样
    改进后:
        时间复杂度上,核心的的计算代码从n^3改进为n(n+1)(2n+1)/3,有一定改进;
        其次,空间复杂度上,图的存储只使用半角矩阵,空间复杂度降低一半,但还是O(V^2)。但就目前来说,仅仅是一次尝试,仍需改进。

​

Union-Find判断:

  • 根据读入的边对进行集合的划分,读入的边对证明是相连的,那么我们就划分到一个集合中,然后读入新的边对就划分到新的集合中,一旦读入的边对(两个顶点)都在同一个集合中出现过,那么证明是相互连通的,如果读入的边对,两个顶点是出现在两个集合中,那么说明这两个集合至少通过该边是可以相连的,那么集合进行合并。重复以上过程,即可得到求解结果。最后只要判断所以结点是否在一个集合就可得到图是否连通,若在一个集合,则图连通,反之不连通。

  • 算法步骤及相关分析:

a)算法:
    unionFindJudge():使用并查集进行集合判断
    关键代码:
        for(int j = 1; j
  • 代码:

Union-Find,并查集类的实现请参考以下博文:http://www.cnblogs.com/noKing/p/8018609.html

public boolean UnionFindJudge() {
	boolean flag = true;
	for(int j = 1; j

Tarjan判断:

  • 该算法可以用来求有向图的强连通分量。在无向图中,两点之间的连通其实等价于有向图中的强连通,都是互相可达的概念。所以,就可以使用Tarjan算法来得到无向图的“强”连通分量。如果无向图只有一个“强”连通分量,那么这个图就是一个连通图。
  • 主要算法步骤及相关分析:

dfn[]:记录一个结点的搜索次序;

low[]:记录结点或结点所在的子树能够追溯到的最早的栈中结点的次序号

其实就是结点i,被访问的时间戳就是dfn[i],low[i]是其所在强连通分量的根。如果这两个值相等,说明i就是树根,那么栈中i上面的元素都是这个强连通分量的元素。

中间的具体操作,实现原理请自己查阅。

  • 代码:

​​​​​​​此处判断依据是:进行一次判断,得到一个强连通分量,如果这个强连通分量的个数与图的点个数想法,就是连通图。

本次代码使用数组模拟栈,读者可自由发挥,知道需要用到栈即可。

  • public boolean tarjan() {
        judgeTarjanCount = 0;					//记录一个连通分量的结点数
    	top = -1; t =0;							//top为栈顶,t为访问结点的时间,全局变量
    
    	tarjan(0);							    //从0号结点开始,只访问一个连通分量
    
    	if(judgeTarjanCount == this.vertexNum)	//如果该连通分量的结点数与图总结点数
    		return true;						//相同,那么图就是连通的
    	else									//否则不连通
    		return false;
    }
    private void tarjan(int current) {
    	dfn[current] = low[current] = ++ t;			//赋初值,为当前时间戳
    	z[++top]=current;  					        //入栈,用数组模拟栈
    	dist[current] = 1;  						//标记current已在栈中
    	for(int i=0;ilow[i])		//low[current]=min{low[current],low[i]}
    					low[current] = low[i];
    			}else if(dist[i] == 1 && low[current] > dfn[i]) {	//若在栈中
    				low[current] = dfn[i]; 		
                                                 //low[current]=min{low[current],dfn[i]}
    			}
    		}
    	}
    	if(low[current] == dfn[current]) {			//如果相等,说明是连通分量
    		do {									    //将连通分量出栈
    			judgeTarjanCount ++;			        //记录连通分量的结点个数
    		}while(z[top--] != current);			
    	}
    }

     

你可能感兴趣的:(无向图连通性判断的五种方法(BFS、DFS、Union-find、Warshell、Tarjan))