一、算法描述
只需用一个递归方法来遍历所有顶点。在访问其中一个顶点时:
- 将它标记为已访问;
- 递归地访问它的所有没有被标记过的邻居顶点。
这种方法称为深度优先搜索( DFS)。可以使用一个 boolean 数组每个顶点是否被访问过。递归方法会标记给定的顶点并调用自己来访问该顶点的相邻顶点列表中所有没有被标记过的顶点。如果图是连通的,每个邻接链表中的元素都会被检查到。
二、api定义
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
三、算法实现
本算法只针对邻接表表示的图结构进行深度优先搜索,其图的创建代码参考上一篇文章无向图的创建(java实现),本算法依赖上一篇文章所建立图。
package graph;
import java.util.*;
/**
* 深度优先搜索
* @author hh
*/
public class DepthFirstSearch {
/**
* 图对象
*/
private Graph graph;
/**
* 开始顶点编号
*/
private int startVer;
/**
* 标记哪些顶点被访问
*/
private boolean[] marked;
/**
* 私有构造函数
*/
private DepthFirstSearch() {
}
/**
* 公有构造函数
* @param graph
* @param startVer
*/
public DepthFirstSearch(Graph graph, int startVer) {
this.graph = graph;
this.startVer = startVer;
marked = new boolean[graph.getVertices()];
Arrays.fill(marked,Boolean.FALSE);
}
/**
* 深度优先搜索
*/
public void dfs(){
for(int v = 0; v < graph.getVertices(); v++ ){
if(!isMarked(v)){
this.search(v);
}
}
}
/**
* 顶点w是否被访问过
* @param w :顶点编号
* @return ;是否被访问
*/
private boolean isMarked(int w){
return this.marked[w];
}
/**
* 深度优先搜索算法实现
* @param v
*/
private void search(int v){
System.out.print(v +" ");
marked[v] = true;
for(Integer w : graph.adj(v)){
if(!isMarked(w)){
search(w);
}
}
}
}
测试代码如下所示:
public class GraphTest {
public static void main(String[] args) throws Exception {
String filename = "D:\\Desktop\\graph1.txt";
Graph graph = new Graph();
graph.createGraphByFile(filename);
DepthFirstSearch depthFirstSearch = new DepthFirstSearch(graph,0);
System.out.print("深度优先搜索:");
depthFirstSearch.dfs();
}
}
四、算法应用
- 计算连通分量:假设一个图有多个连通分量(多个子图),我们可以对图中的没有访问顶点进行dfs,如果该顶点没有被访问,那么连通分量计数加1,然后对该顶点进行dfs,这样该顶点所在子图的所有顶点已被访问,继续执行上面算法直至所有顶点已被访问。
- 寻找路径:通过dfs我们可以使用一数组edgeTo记录起点s到任意顶点v的路径,其中edgeTo[w] = v表示访问w时上一次访问的是v顶点,这一巧妙设计 就可以计算出起点s到任意顶点v的路径。
完整实现代码如下:
package graph;
import java.util.*;
/**
* 深度优先搜索
* @author hh
*/
public class DepthFirstSearch {
/**
* 图对象
*/
private Graph graph;
/**
* 开始顶点编号
*/
private int startVer;
/**
* 标记哪些顶点被访问
*/
private boolean[] marked;
/**
* dfs访问路径记录
*/
private int[] edgeTo;
/**
* 记录每个顶点的连通分量标识
*/
private int[] id;
/**
* 联通分量数量
*/
private int cc;
/**
* 私有构造函数
*/
private DepthFirstSearch() {
}
/**
* 公有构造函数
* @param graph
* @param startVer
*/
public DepthFirstSearch(Graph graph, int startVer) {
this.graph = graph;
this.startVer = startVer;
marked = new boolean[graph.getVertices()];
edgeTo = new int[graph.getVertices()];
id = new int[graph.getVertices()];
cc = 0;
Arrays.fill(marked,Boolean.FALSE);
Arrays.fill(edgeTo,0);
Arrays.fill(id,0);
}
/**
* 深度优先搜索
*/
public void dfs(){
for(int v = 0; v < graph.getVertices(); v++ ){
if(!isMarked(v)){
//连通分量加1
this.cc ++;
this.search(v);
}
}
}
/**
* 返回当前顶点的连通分量标识
*
* @param v :顶点编号
* @return :当前顶点的连通分量标识
*/
public int id(int v){
return id[v];
}
/**
* 顶点v和w连通
*
* @param v :顶点v编号
* @param w :顶点w编号
* @return :true标识连通,false标识不连通
*/
public boolean isConnect(int v,int w){
return id(v) == id(w);
}
/**
*
* @return : 连通分量数量
*/
public int cc(){
return cc;
}
/**
* 查找起点s到w路径集合
*
* @param w : 顶点编号
* @return :起点s到w路径集合
*/
public Collection<Integer> pathTo(int w){
return GraphUtils.pathTo(this.edgeTo,this.startVer,w);
}
/**
* 顶点w是否被访问过
* @param w :顶点编号
* @return ;是否被访问
*/
private boolean isMarked(int w){
return this.marked[w];
}
/**
* 深度优先搜索算法实现
* @param v
*/
private void search(int v){
System.out.print(v +" ");
marked[v] = true;
id[v] = cc;
for(Integer w : graph.adj(v)){
if(!isMarked(w)){
//记录访问顶点w的前一次访问顶点路径
this.edgeTo[w] = v;
search(w);
}
}
}
}
package graph;
import java.util.Collection;
import java.util.LinkedList;
/**
* 图的工具类
* @author hh
*/
public class GraphUtils {
/**
* 打印图中起点到终点路径
*
* @param edgeTo : 路径数组
* @param s : 起点编号
* @param w : 终点编号
* @return : 路径顶点列表
*/
public static Collection<Integer> pathTo(int[] edgeTo, int s, int w){
LinkedList<Integer> path = new LinkedList<>();
path.addFirst(w);
int v = edgeTo[w];
while (v != s){
path.addFirst(v);
v = edgeTo[v];
}
path.addFirst(s);
return path;
}
}