LeetCode 中有很多关于图的dfs 和 bfs 与回溯等算法结合的算法题,例如最短路径和等。每次当我遇到这些题时,总是无法独立的写出较好的解法,我觉得这是因为我对数据结构图的理解不够,所以就重新看以前的数据结构与算法分析的书籍,以此加深自己对图的理解。
关于图的一些基本知识和存储结构等如果有兴趣的话可以去参考相关书籍,我这里主要写的是用邻接矩阵作为存储结构的连通图和非连通图的一些操作以及图的 dfs(深度优先搜索)和 bfs(广度优先搜索)。
与树的遍历操作类同,图的遍历操作的定义是,访问图中的每个顶点且每个顶点只能被访问一次。图的遍历方法主要有两种:一种是深度优先搜索。另一种是广度优先搜索。图的深度优先搜索类同于树的先根遍历,图的广度优先遍历类同于树的层序遍历。
图的遍历算法需要考虑三点:
连通图的深度优先遍历算法
在图的所有邻接顶点中,每次都在访问完当前顶点后,首先访问当前顶点的第一个邻接顶点。
深度优先遍历算法可以设计为递归和非递归算法。、
以下是递归算法的步骤:
该递归算法用到回溯的算法思想,当寻找顶点 v 的邻接顶点 w 成功时,继续进行;失败时,回溯到上一次递归调用的地方继续进行。
连通图的广度优先遍历算法
广度优先算法是一个分层搜索的过程,从指定顶点开始,按照到该顶点的顺序,以便按顺序访问这些顶点的邻接顶点,依次访问图中的其余顶点。
图的广度优先遍历算法需要一个队列(或栈)来保存访问过的顶点的顺序,以便按顺序访问这些顶点的邻接顶点。其算法思想如下:
非连通图的遍历算法
对于非连通图,从图的任意一个初始顶点开始深度或广度遍历,都不能访问图中的所有顶点,这时这能访问和初始顶点联通的那些顶点。
但是,对于非连通图,可以一次把每个顶点作为初始顶点进行一次深度或广度优先遍历,并根据每个顶点的访问标记来判断该顶点是否已访问过。若尚未访问过,则访问,否则跳过该顶点。这样,也能访问到非连通图中的每个顶点。
邻接矩阵存储结构
public class AMWGraph {
private ArrayList list; // 存储顶点
private int[][] edges; // 邻接矩阵,用于存储边
private int numOfEdges; // 边的数目
public AMWGraph(int n) {
// 初始化矩阵、一维数组、边的数目
edges = new int[n][n];
list = new ArrayList<>(n);
numOfEdges = 0;
}
// 得到结点的个数
public int getNumOfVertex() {
return list.size();
}
// 得到边的条数
public int getNumOfEdges() {
return numOfEdges;
}
// 返回结点 i 的值
public Object getValueByIndex(int i) {
return list.get(i);
}
// 返回两个结点对应的边的权值
public int getWeight(int v1,int v2) {
return edges[v1][v2];
}
// 插入结点
public void insertVertex(Object vertex) {
list.add(list.size(), vertex);
}
// 插入边
public void insertEdge(int v1,int v2,int weight) {
edges[v1][v2] = weight;
numOfEdges++;
}
// 删除边
public void deleteEdge(int v1,int v2) {
edges[v1][v2] = 0;
numOfEdges--;
}
//得到第一个邻接结点的下标
public int getFirstNeighbor(int index) {
for(int j=0;j<list.size();j++) {
if (edges[index][j]>0) {
return j;
}
}
return -1;
}
//根据前一个邻接结点的下标来取得下一个邻接结点
public int getNextNeighbor(int v1,int v2) {
for (int j=v2+1;j<list.size();j++) {
if (edges[v1][j]>0) {
return j;
}
}
return -1;
}
}
测试
public class TestAMWGraph {
public static void main(String[] args) {
// 结点数和边数
int n = 4, e = 4;
// 结点的标识
String[] labels ={"V1","V1","V3","V4"};
AMWGraph graph=new AMWGraph(n);
for(String label:labels) {
graph.insertVertex(label);//插入结点
}
//插入四条边
graph.insertEdge(0, 1, 2);
graph.insertEdge(0, 2, 5);
graph.insertEdge(2, 3, 8);
graph.insertEdge(3, 0, 7);
System.out.println("结点个数是:"+graph.getNumOfVertex());
System.out.println("边的个数是:"+graph.getNumOfEdges());
graph.deleteEdge(0, 1);//删除边
System.out.println("删除边后..." );
System.out.println("结点个数是:"+graph.getNumOfVertex());
System.out.println("边的个数是:"+graph.getNumOfEdges());
System.out.println(graph.getNextNeighbor(1, 2));
}
}
连通图的深度优先遍历
// 连通图的深度优先遍历
private void depthFirstSearch(boolean[] isVisited,int i) {
System.out.print(getValueByIndex(i) + " ");
// 设置该结点已经访问
isVisited[i] = true;
int w = getFirstNeighbor(i);
while(w != -1) {
if(!isVisited[w]) {
// 没有被访问
depthFirstSearch(isVisited ,w);
}
// 若已经被访问
w=getNextNeighbor(i, w);
}
}
非连通图的深度优先遍历
public void depthFirstSearch(boolean[] isVisited) {
for(int i=0;i<getNumOfVertex();i++) {
//因为对于非连通图来说,并不是通过一个结点就一定可以遍历所有结点的。
if (!isVisited[i]) {
depthFirstSearch(isVisited,i);
}
}
}
连通图的广度优先遍历
private void broadFirstSearch(boolean[] isVisited,int i) {
int u,w;
LinkedList queue=new LinkedList();
//访问结点i
System.out.print(getValueByIndex(i)+" ");
isVisited[i]=true;
//结点入队列
queue.addLast(i);
while (!queue.isEmpty()) {
u=((Integer)queue.removeFirst()).intValue();
w=getFirstNeighbor(u);
while(w!=-1) {
if(!isVisited[w]) {
//访问该结点
System.out.print(getValueByIndex(w)+" ");
//标记已被访问
isVisited[w]=true;
//入队列
queue.addLast(w);
}
//寻找下一个邻接结点
w=getNextNeighbor(u, w);
}
}
}
非连通图的广度优先遍历
public void broadFirstSearch(boolean[] isVisited) {
for(int i=0;i<getNumOfVertex();i++) {
if(!isVisited[i]) {
broadFirstSearch(isVisited, i);
}
}
}