目录
图的遍历
一、图的广度优先遍历(bfs)
二、图的深度优先遍历
图的遍历
给定一个图G和其中任意一个顶点v0,从v0出发,沿着图中各边访问图中的所有顶点,且每个顶点仅被遍历一次。"遍历"即对结点进行某种操作的意思。
在二叉树中,我们对树的最多的操作就是各种遍历,而二叉树的前、中、后序遍历都是深度优先遍历,层序遍历就是广度优先遍历。
而二叉树可以被视为一种特殊类型的图。具体来说,二叉树是一种特殊的有向无环图(Directed Acyclic Graph,DAG)。
在一棵二叉树中,每个节点最多有两个子节点,分别称为左子节点和右子节点。这种结构符合图的定义,其中节点之间通过边(或连接)相互关联。
注意:两个遍历均基于【图(1)】上篇博客的邻接矩阵实现。
一、图的广度优先遍历(bfs)
类似于树,我们从起始顶点开始,同样一层一层遍历:
还是用这个简单图举例:
假设以B顶点开始广度优先遍历:
结合邻接矩阵我们可以清晰得出结论:每遍历一层顶点后,下一层需要遍历的顶点为该层顶点能指向的所有顶点。
下面模拟一下过程(与树的层序遍历相同,需要一个队列):
- 从B开始,遍历矩阵B对应的这一行,权值不为0的即B指向的顶点(A,C),将它们入队。
- A,C入队,对A,C进行相同的操作(poll)。而遍历A这一行发现A又指向了B,但B已经访问过,不能再次访问,因此在遍历前我们需要一个数组来记录每个顶点是否被访问过。再看A指向D,将D入队。
- poll出C,遍历C这一行,发现B,D都已经访问过,不需要操作。
- poll出D,同上。得到结果:B->A->C->D
综上,只要对二叉树的各种遍历非常熟悉(这里主要是层序遍历),然后刷过一些层序遍历、递归回溯的算法,图的广度优先遍历就非常简单了。
/*** 图的广度优先遍历** @param v 开始遍历的顶点*/public void bfs(char v) {//1.定义一个数组标记当前顶点是否已经访问过boolean[] visited = new boolean[arrayV.length];//2.申请队列进行bfsQueue<Integer> queue = new ArrayDeque<>();int index = getIndexOfV(v); //记录当前顶点下标,也对应要遍历的行数queue.offer(index);visited[index] = true; //v位置设置为truewhile (!queue.isEmpty()) {index = queue.poll();System.out.print(arrayV[index] + " ");//遍历矩阵的第index行for (int i = 0; i < arrayV.length; i++) {//当前有连通节点且visited为false才能入队if (matrix[index][i] != 0 && !visited[i]) {queue.offer(i);visited[i] = true;}}}}
测试(基于上篇博客的邻接矩阵类):
public static void main(String[] args) {GraphByMatrix graphByMatrix = new GraphByMatrix(4, false);char[] array = {'A', 'B', 'C', 'D'};graphByMatrix.initArrayV(array);graphByMatrix.addEdge('A', 'B', 1);graphByMatrix.addEdge('A', 'D', 1);graphByMatrix.addEdge('B', 'A', 1);graphByMatrix.addEdge('B', 'C', 1);graphByMatrix.addEdge('C', 'B', 1);graphByMatrix.addEdge('C', 'D', 1);graphByMatrix.addEdge('D', 'A', 1);graphByMatrix.addEdge('D', 'C', 1);//graphByMatrix.printGraph();System.out.println("顶点A的度:" + graphByMatrix.getDevOfV('A'));System.out.print("bfs: ");graphByMatrix.bfs('B');System.out.println();System.out.print("dfs: ");graphByMatrix.dfs('B');}
二、图的深度优先遍历(dfs)
图例:
还是来看前面那个简单的图:
总结:每次访问该顶点指向的第一个顶点,直到最后一个顶点指向的所有顶点都被访问过,开始回溯。
下面还是模拟一下过程:
- 从顶点B开始,遍历B这一行,发现B指向A,递归到A这一层再访问。
- 同样遍历A这一行,发现A又指向B了,那我们肯定不能进入这一层(回溯算法中的剪枝操作)。因此再遍历的同时同样需要一个标记数组来记录顶点是否被访问过。再继续遍历A,发现A指向D,D没被访问过,再递归进入D这一层。
- 遍历D这一行,D指向A,剪枝;继续遍历,D指向C,C没被访问过,再递归进入C这一层。
- 遍历C,发现C所有指向的顶点都已经被访问过。遍历结束后开始回溯。
- 回溯过程中,发现所有顶点都已经访问过,即都会被剪掉,因此再不会进入任何的递归操作。直到回到最开始的B这一层,循环结束,遍历完成。顺序:B->A->D->C。
/*** 图的深度优先遍历** @param v 起始顶点*/public void dfs(char v) {visited = new boolean[arrayV.length];int index = getIndexOfV(v);dfsChild(index);}boolean[] visited; //记录当前顶点是否被访问过private void dfsChild(int index) {System.out.print(arrayV[index] + " ");visited[index] = true;//看矩阵中index这行, 是否还有未访问过的顶点for (int i = 0; i < arrayV.length; i++) {if (matrix[index][i] != 0 && !visited[i]) {dfsChild(i);}}}
测试(基于上篇博客的邻接矩阵类):
public static void main(String[] args) {GraphByMatrix graphByMatrix = new GraphByMatrix(4, false);char[] array = {'A', 'B', 'C', 'D'};graphByMatrix.initArrayV(array);graphByMatrix.addEdge('A', 'B', 1);graphByMatrix.addEdge('A', 'D', 1);graphByMatrix.addEdge('B', 'A', 1);graphByMatrix.addEdge('B', 'C', 1);graphByMatrix.addEdge('C', 'B', 1);graphByMatrix.addEdge('C', 'D', 1);graphByMatrix.addEdge('D', 'A', 1);graphByMatrix.addEdge('D', 'C', 1);//graphByMatrix.printGraph();System.out.println("顶点A的度:" + graphByMatrix.getDevOfV('A'));System.out.print("bfs: ");graphByMatrix.bfs('B');System.out.println();System.out.print("dfs: ");graphByMatrix.dfs('B');}