无向图:即图的边是没有方向属性的。

单点路径

最短单点路径

连通分量

环检测

图的二分性


DFS可以用来解决单点路径,是否有环,连通分量,图的二分性等问题;BFS可以用来解决最短单点路径问题。

单点路径

给定无向图,判断图中某一个顶点到其他顶点的连通路径问题,如果存在单点路径,则输出该路径。

给定以下无向图以及起始点0,求出0到其余各顶点的单点路径

                                                                                 

无向图绘制 python 无向图 算法_数组

对于给定图进行DFS深度优先搜索,利用marked数组来标识某个顶点是否访问过,利用edgeTo数组来保存最后一次到达该顶点的顶点,即edgeTo[w]=v表示由顶点v可以直接到达顶点w。要求起始点s到顶点x的单点路径,则从edgeTo[x]开始,依次往回寻找x=edgeTo[x]直到x==s,将该路径上的顶点值利用栈来保存,最后依次弹出栈中元素便可得到由s到x的单点路径。

代码如下:

//无向图  深度优先搜索  单点路径
public class DepthFirstPaths {
	
	private boolean[] marked;
	//edgeTo[w]=v的含义是由v可以直接到达w
	private int[] edgeTo;
	private final int s;
	
	public DepthFirstPaths(Graph g, int s){
		this.marked = new boolean[g.V()];
		this.edgeTo = new int[g.V()];
		this.s = s;
		dfs(g, s);
	}
	
	private void dfs(Graph g, int v) {
		marked[v] = true;
		for(int w : g.adj(v)){
			if(!marked[w]){
				//向v下一个指向w
				//edgeTo[x]得到谁指向x
				edgeTo[w] = v;
				dfs(g, w);
			}
		}
	}

	//起点s到节点v是否存在单点路径
	public boolean hasPathTo(int v){
		return marked[v];
	}
	
	//返回s-->v路径
	public Iterable<Integer> pathTo(int v){
		if(!hasPathTo(v)) return null;
		//利用栈倒序存储
		Stack<Integer> s = new Stack<>();
		//由最终节点向起始节点寻找  edgeTo[x]表示谁指向x
		for(int x=v; x!=this.s; x = edgeTo[x]){
			s.push(x);
		}
		s.push(this.s);
		return s;
	}
	
}

上图邻接表数组表示如下:

0: 2--->1--->5
1: 0--->2
2: 0--->1--->3--->4
3: 5--->4--->2
4: 3--->2
5: 3--->0

进行一次DFS搜索得到marked,edgeTo数组

x edgeTo[x]
0     0
1     2
2     0
3     2
4     3
5     3
[true, true, true, true, true, true]

edgeTo数组保存的信息是在无向图中,以起点s为根,到达各连通节点的一棵树。

最终起点0到其余顶点的单点路径如下图所示:

                                                 

无向图绘制 python 无向图 算法_连通分量_02

0:1    0-->2-->1
0:2    0-->2
0:3    0-->2-->3
0:4    0-->2-->3-->4
0:5    0-->2-->3-->5

DFS可以解决单点路径,判断两个点的连通性问题,但是得到的单点路径并不一定是最短的。

最短单点路径

给定无向图以及一个起始点s,求出起始点到其余各顶点的最短单点路径。

                                                                                        

无向图绘制 python 无向图 算法_无向图绘制 python_03

对于图进行BFS广度优先搜索,利用marked数组来记录访问过的顶点,利用edgeTo[v]数组来保存最后一次来访问v的顶点。

代码如下:

//广度优先搜索  无向图  单点最短路径
public class BreadthFirstPaths {
	
	private boolean[] marked;
	private int[] edgeTo;
	private final int s;
	
	public BreadthFirstPaths(Graph g, int s){
		marked = new boolean[g.V()];
		edgeTo = new int[g.V()];
		this.s = s;
		bfs(g, s);
	}

	private void bfs(Graph g, int s) {
		Queue<Integer> q = new LinkedList<>();
		marked[s] = true;
		q.add(s);
		while(!q.isEmpty()){
			int v = q.poll();
			for(int w : g.adj(v)){
				if(!marked[w]){
					edgeTo[w] = v;
					marked[w] = true;
					q.add(w);
				}
			}
		}	
	}
	
	//起点s到节点v是否存在单点路径
	public boolean hasPathTo(int v){
		return marked[v];
	}
	
	//返回s-->v路径
	public Iterable<Integer> pathTo(int v){
		if(!hasPathTo(v)) return null;
		//利用栈倒序存储
		Stack<Integer> s = new Stack<>();
		//由最终节点向起始节点寻找  edgeTo[x]表示谁指向x
		for(int x=v; x!=this.s; x = edgeTo[x]){
			s.push(x);
		}
		s.push(this.s);
		return s;
	}
	
	public List<Integer> pathTo2(int v){
		if(!hasPathTo(v)) return null;
		//利用栈倒序存储
		Stack<Integer> s = new Stack<>();
		//由最终节点向起始节点寻找  edgeTo[x]表示谁指向x
		for(int x=v; x!=this.s; x = edgeTo[x]){
			s.push(x);
		}
		s.push(this.s);
		List<Integer> l = new ArrayList<>();
		while(!s.isEmpty()){
			l.add(s.pop());
		}
		return l;
	}
	
	public String pathTo3(int v){
		if(!hasPathTo(v)) return null;
		List<Integer> l = pathTo2(v);
		StringBuilder sb = new StringBuilder();
		sb.append(s + ":" + v + "    ");
		for(int i=0; i<l.size(); i++){
			sb.append(l.get(i));
			if(i!=l.size()-1){
				sb.append("-->");
			}
		}
		return sb.toString();
	}
}

最终结果显示如下:

                                                      

无向图绘制 python 无向图 算法_无向图_04

 

0:1    0-->1
0:2    0-->2
0:3    0-->2-->3
0:4    0-->2-->4
0:5    0-->5

由于BFS首先访问距离顶点最近的所有顶点,BFS中使用队列来保存访问过的节点,距离起始点越近的顶点越早进入队列,越早离开队列,一旦离开队列,说明该顶点已经访问,标记该节点已被访问,之后不会再次访问,因此edgeTo数组中此时保存的路径是最短路径。

连通分量

给定无向图,找出图中的所有连通分量。

                                                                

无向图绘制 python 无向图 算法_无向图绘制 python_05

利用DFS进行深度优先搜索,marked数组保存访问过的节点,count用来计数连通分量总数,id数组用来保存每个顶点的连通分量序号;

对于图的顶点数组进行遍历,对于每一个顶点,进行DFS搜索可以获取与该顶点相连的所有顶点,将这些顶点利用id数组来统一标识;然后寻找下一个没有被标记的顶点,说明该顶点时另外一个连通分量,count++,继续进行DFS。

//无向图  深度优先搜索  所有连通分量
public class CC {
	
	private boolean[] marked;
	//将每个节点所在连通分量的编号记录下来
	private int[] id;
	//连通分量总数
	private int count;
	
	public CC(Graph g){
		marked = new boolean[g.V()];
		id = new int[g.V()];
		for(int s=0; s<g.V(); s++){
			if(!marked[s]){
				dfs(g, s);
				count++;
			}
		}
	}
	
	private void dfs(Graph g, int s) {
		marked[s] = true;
		id[s] = count;
		for(int v : g.adj(s)){
			if(!marked[v]){
				dfs(g, v);
			}
		}
	}

	public boolean connected(int v, int w){ 
		return id[v] == id[w];
	}
	
	public int count(){
		return count;
	}
	
	public int id(int v){
		return id[v];
	}
}

得到最终连通分量结果如下:   

                                                         

无向图绘制 python 无向图 算法_数组_06

环检测

检测给定的图是否含有环。

对于每次dfs调用,从起始点s出发进行DFS深度优先搜索,对于即将访问某一个节点v时,记录v的上一个节点u,如果访问到v已经被标记过,并且v!=u,则说明有环;如果v==u,此时v与u直接相连,并不能构成环。

如图:

                                                                               

无向图绘制 python 无向图 算法_数组

邻接表数组为:

0: 2--->1--->5
1: 0--->2
2: 0--->1--->3--->4
3: 5--->4--->2
4: 3--->2
5: 3--->0

对于每一个未被标记的点s来进行dfs深度优先搜索,如果s的邻接表第一个节点未被标记,则继续dfs,如果已被标记,则判断该第一个节点是否为s,如果为s说明属于直接回退,例如从0到1,在从1到0,此时不属于有环情况;如果该节点已被标记,并且不等于s,则说明有环。

//无向图  深度优先搜索  是否有环
public class Cycle {
	
	private boolean[] marked;
	
	private boolean hasCycle = false;
	
	public Cycle(Graph g){
		marked = new boolean[g.V()];
		//遍历每一个节点
		for(int s=0; s<g.V(); s++){
			//如果该节点没有被标记过,则进行深度优先搜索
			if(!marked[s]){
				dfs(g, s, s);
			}
		}
	}
	
	public boolean hasCycle(){
		return hasCycle;
	}

	private void dfs(Graph g, int s, int u) {
		marked[s] = true;
		for(int v : g.adj(s)){
			System.out.println(s + " " + u + " " + v + " " + marked[v]);
			//搜索节点s的所有临接节点v 如果v没有标记过,继续dfs
			if(!marked[v]){
				dfs(g, v, s);
			}
			//u保存访问v的上一个节点
			//如果已经标记过,如果此时v==u 说明v直接相连于u,即u-->v 此时并没有环
			else if(v != u){
				hasCycle = true;
				System.out.println("end");
				return;
			}
		}
	}
}

图的二分性

给定无向图,判断图是否具有二分性,即将图的每一个顶点置为红色或者黑色,要求同一条边的两个顶点颜色不一样。

进行DFS遍历搜索时,由某一个顶点进而访问下一个顶点,判断下一个待访问的顶点是否访问过,如果没有访问过,则将下一个顶点与本顶点颜色设为相反,如果访问过,并且颜色一致,则说明图不能二分。

//无向图  深度优先搜索  是否具有二分性
public class TwoColor {
	
	private boolean[] marked;
	
	private boolean[] color;
	
	private boolean isTwoColorable = true;
	
	public TwoColor(Graph g){
		marked = new boolean[g.V()];
	    color = new boolean[g.V()];
		//遍历每一个节点
		for(int s=0; s<g.V(); s++){
			//如果该节点没有被标记过,则进行深度优先搜索
			if(!marked[s]){
				dfs(g, s);
			}
		}
	}
	
	public boolean isBipartite(){
		return isTwoColorable;
	}

	private void dfs(Graph g, int s) {
		marked[s] = true;
		for(int v : g.adj(s)){
			//此时访问s的临接顶点v
			if(!marked[v]){
				//将v颜色设置为与s相反
				color[v] = !color[s];
				dfs(g, v);
			}
			//如果已经标记过,并且颜色相同,则说明图不具有二分性
			else if(color[v]==color[s]){
				isTwoColorable = false;
				return;
			}
		}
	}
}