算法一般跟数据结构是联系在一起的,数据关系一般有一对一、一对多、多对多。其中,一对一可以理解为线性表,如数组和链表这些;一对多可以理解为树,比如二叉树,一个根节点对两个子节点;多对多则可以理解为图,比如接下来提供的范例:
上面就是图(graph)的一个示例,A对应C和D,而D对应A和C,C就更多了,对应ADBF……
上面的示例图,每个带数据的英文字符和圈圈,叫图的顶点(Vertex),顶点间由线连接来确定连通关系。当然连通线可以无向,也可以有向,甚至带权。本例则是无向的连接线,也就是双方都可以互通
进入正题,比如我们从顶点A出发,怎么遍历整幅图的所有顶点呢?有两种方案:一种是尽可能深地走,就像找路一样,不走到死胡同则一直走,走到死胡同了则退回来再探寻第二个方向;另一种是先找遍下一个路口,然后再找遍下下个路口,直到所有都是死胡同。其中第一个叫做"深度优先搜索",英文缩写为DFS,第二个叫"广度优先搜索",英文缩写为BFS;
下面我们创建图顶点类,以及生成上面图例的方法,此方法返回搜索起点的顶点,也就是A顶点:
import java.util.HashSet;
import java.util.Set;
/**
* 图的顶点类
* @Author: LiYang
* @Date: 2019/9/1 1:11
*/
public class Vertex {
//顶点的值
public String value;
//是否被访问过的标记
public boolean visited;
//相邻顶点
public Set<Vertex> neighbors = new HashSet<>();
/**
* 构造方法,传入顶点的值
* @param value
*/
public Vertex(String value) {
this.value = value;
}
/**
* 顶点添加相邻节点,可链式添加
* @param neighbor
* @return
*/
public Vertex addNeighbors(Vertex neighbor){
neighbors.add(neighbor);
return this;
}
/**
* 创建示例图
* @return
*/
public static Vertex getExampleGraph(){
//图的顶点
Vertex vertexA = new Vertex("A");
Vertex vertexB = new Vertex("B");
Vertex vertexC = new Vertex("C");
Vertex vertexD = new Vertex("D");
Vertex vertexE = new Vertex("E");
Vertex vertexF = new Vertex("F");
Vertex vertexG = new Vertex("G");
Vertex vertexH = new Vertex("H");
Vertex vertexI = new Vertex("I");
Vertex vertexJ = new Vertex("J");
Vertex vertexK = new Vertex("K");
//按照示例图,添加相邻顶点
vertexA.addNeighbors(vertexC).addNeighbors(vertexD);
vertexB.addNeighbors(vertexE).addNeighbors(vertexC);
vertexC.addNeighbors(vertexB).addNeighbors(vertexF).addNeighbors(vertexD).addNeighbors(vertexA);
vertexD.addNeighbors(vertexA).addNeighbors(vertexC);
vertexE.addNeighbors(vertexH).addNeighbors(vertexI).addNeighbors(vertexF).addNeighbors(vertexB);
vertexF.addNeighbors(vertexG).addNeighbors(vertexC).addNeighbors(vertexE);
vertexG.addNeighbors(vertexF);
vertexH.addNeighbors(vertexE).addNeighbors(vertexJ);
vertexI.addNeighbors(vertexE).addNeighbors(vertexK);
vertexJ.addNeighbors(vertexK).addNeighbors(vertexH);
vertexK.addNeighbors(vertexI).addNeighbors(vertexJ);
//返回搜索的起始顶点
return vertexA;
}
}
接下来我们通过Java的代码,实现 “深度优先搜索(DFS)” 和 " 广度优先搜索(BFS) ",有兴趣的朋友可以照着示例代码,用Debug模式一步步探寻这两种图的搜索方式的机理:
import java.util.LinkedList;
import java.util.Queue;
/**
* 图的类
* @Author: LiYang
* @Date: 2019/9/1 1:20
*/
public class Graph {
/**
* 深度优先搜索 DFS
* @param vertex 起始顶点
*/
public static void DFS(Vertex vertex){
//如果该顶点已被访问,则结束递归
if (vertex.visited){
return;
}
//打印顶点的值(访问)
System.out.print(vertex.value + " ");
//设置顶点的访问状态为已访问
vertex.visited = true;
//递归搜索相邻节点
for (Vertex neighbor : vertex.neighbors){
DFS(neighbor);
}
}
/**
* 广度优先搜索 BFS
* @param vertex 起始顶点
*/
public static void BFS(Vertex vertex){
//新建一个队列,用LinkedList实现
Queue<Vertex> graphQueue = new LinkedList<>();
//加入搜索起始顶点
graphQueue.add(vertex);
//队列未空,则还有可搜索的顶点
while (graphQueue.size() > 0){
//队列出队一个顶点
Vertex currentVertex = graphQueue.poll();
//如果顶点没有被访问
if (!currentVertex.visited){
//打印顶点的值(访问)
System.out.print(currentVertex.value + " ");
//设置顶点的访问状态为已访问
currentVertex.visited = true;
//出队的那个顶点访问后,将其相邻顶点入队
for (Vertex neighbor : currentVertex.neighbors){
graphQueue.add(neighbor);
}
}
}
}
/**
* 测试深度优先搜索和广度优先搜索
* @param args
*/
public static void main(String[] args) {
//深度优先搜索
System.out.print("DFS:");
DFS(Vertex.getExampleGraph());
//换行
System.out.println();
//广度优先搜索
System.out.print("BFS:");
BFS(Vertex.getExampleGraph());
}
}
最后,执行main方法,控制台打印两种搜索方法遍历图顶点的顺序:
DFS:A D C B E H J K I F G
BFS:A D C B F E G H I J K