今天回顾了DFS——Depth First Search——深度优先遍历,这个算法主要有两个用途:一是用于对于未知解的探索,一个典型的例子是走迷宫,也就是我们要列出所有的可能性来穷举,如果找到一条可行之路那么说明我们要解决的问题有戏,如果到最后也没有找到一条可行之路,那么说明我们的问题没有解。二是作为拓扑排序的基石,这一点我们以后再讲。
那么如何来实现DFS算法呢? 我们需要的原料有:结点、边、结点数、以及一个很重要的标记数组——来标记某个结点是否已经被访问过了,这可以避免我们在图中来来回回鬼打墙。
public class DFS {
private char[] vertices;
private int verticeNum;
private boolean[] isVisited;
private int[][] edges;
第二步我们通过这个类的一个有参构造函数初始化一下这些原始材料。
public DFS(int n) {
vertices = new char[n];
verticeNum = n;
isVisited = new boolean[n];
for(int i = 0; i<n;i++){
isVisited[i] = false;
}
edges = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
edges[i][j] = 0;
}
}
}
第三步,我们实现加边、加点、访问结点的函数封装,为后续代码的简洁直观打基础
public void addEdge(int i,int j){
//避免加入起点和终点相同的边,其实也就是一个点了……
if(i==j)
return;
edges[i][j] = 1;
edges[j][i] = 1;
}
public void setVertices(char[] vertices){
this.vertices = vertices;
}
public void visit(int i){
System.out.print(vertices[i] + " ");
}
第四步,是一个很重要很核心的递归函数,基本思想就是,深度访问到某一个结点时,访问它并将之标记为已访问。然后在所有的结点中,找这个结点的未访问过的邻结点(也就是和这个结点 由边连接的结点),对邻接点进行深度上的访问。
public void traverse(int i){
isVisited[i] = true;
visit(i);
for(int j = 0; j<verticeNum;j++){
if(edges[i][j] ==1 && isVisited[j] ==false){
traverse(j);
}
}
}
第五步,在DFS算法的主方法中对图中的每一个结点进行遍历。 我们可以看到,DFSTraverse 函数对每一个结点调用递归函数traverse,traverse函数中有有用到visit函数的地方,我觉得这种封装很棒。
public void DFSTraverse(){
for(int i = 0; i< verticeNum;i++){
if(!isVisited[i] )
traverse(i);
}
}
最后,在main函数中实例化,用例子来试一下。
public static void main(String[] args) {
DFS g = new DFS(9);
char[] vertices = {'A','B','C','D','E','F','G','H','I'};
g.setVertices(vertices);
g.addEdge(0, 1);
g.addEdge(0, 5);
g.addEdge(1, 0);
g.addEdge(1, 2);
g.addEdge(1, 6);
g.addEdge(1, 8);
g.addEdge(2, 1);
g.addEdge(2, 3);
g.addEdge(2, 8);
g.addEdge(3, 2);
g.addEdge(3, 4);
g.addEdge(3, 6);
g.addEdge(3, 7);
g.addEdge(3, 8);
g.addEdge(4, 3);
g.addEdge(4, 5);
g.addEdge(4, 7);
g.addEdge(5, 0);
g.addEdge(5, 4);
g.addEdge(5, 6);
g.addEdge(6, 1);
g.addEdge(6, 3);
g.addEdge(6, 5);
g.addEdge(6, 7);
g.addEdge(7, 3);
g.addEdge(7, 4);
g.addEdge(7, 6);
g.addEdge(8, 1);
g.addEdge(8, 2);
g.addEdge(8, 3);
System.out.print("深度优先遍历(递归):");
g.DFSTraverse();
}
我们可以得到结果:深度优先遍历(非递归):A B C D E F G H I
PS:附赠非递归DFS实现。
其基本思想就是,用到栈,利用栈后进先出的结构特点,每弹出一个结点,就将此结点的未访问过的邻结点放入栈,这样下次从栈中取出的就是往深处走的邻结点,当走到头的时候再取出来的结点就是返回遇到的第一个分岔路口了。
public void DFSNoRecurion(){
Stack<Integer> stack = new Stack<Integer>();
for(int i = 0; i< verticeNum;i++){
if(!isVisited[i] ){
stack.push(i);
do{
int cur = stack.pop();
if(!isVisited[cur]){
visit(cur);
isVisited[cur] = true;
for(int j =0;j < verticeNum;j++){
if(edges[cur][j] ==1 && !isVisited[j]){
stack.push(j);
break;
}
}
}
}while(!stack.isEmpty());
}
}
}