《算法导论》——深度优先搜索与拓扑排序


深度遍历算法描述

算法描述参考自《算法导论》深度优先搜索算法:

/* 注解: 1、G.v表示途中节点的集合,其中G是一个有向图 2、G:Adj[u] 表示再有向图中以u为起始节点的邻接节点集合 3、color 白色表示节点未被发现;灰色表示节点已经被发现但没有深搜完毕;黑色节点表示节点深搜完毕 4、u.d 表示节点访问的开始时间。u.f表示节点访问的结束时间。u.parent表示u的父节点,NIL表示父节点为空。 */ DFS(G) for each vertex u belong to G.V u.color=WHITE u.parent=NIL time=0
    for each vertex u belong to G.V if u.color=WHITE DFS-VISIT(G,u) DFS-VISIT(G,u) time=time+1 u.d=time u.color=GRAY for each v belong to G:Adj[u] if v.color==WHITE v.parent=u DFS-VISIT(G,v) u.coloe=BALCK time=time+1 u.f=time

什么是拓扑排序

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。——百度百科

注意:若有向图中存在回路,则该有向图无法进行拓扑排序

 

常用的拓扑排序方法如下:

 

方法一:入度统计
(1)从有向图中选择一个没有前驱(即入度为0)的顶点并且输出它。

(2)从图中删去该顶点,并且删去从该顶点发出的所有边。

(3)重复上述步骤(1)和(2),直到当前有向图中不存在没有前驱结点的顶点为止,或者当前有向图中的所有结点均已输出为止。

(4)如果当前有向图中不存在没有前驱结点的顶点,并且当前有向图中的所有结点尚未完全输出,则可以判断当前有向图中有环

 

方法二:深度优先搜索

思路是记录各个节点深度遍历时完成访问的结束时间,然后根据结束时间的先后顺序组成一个列表,则该列表就是一个拓扑序列

 

拓扑排序DFS算法描述

算法描述参考自《算法导论》拓扑排序:

TOPOLOGICAL-SORT(G) call DFS(G) to compute finishing times v.f for each vertex v as each vertex is finished,insert it onto the front of a linked list return the linked list of vertices

需要注意的是,基于深度优先搜索来实现拓扑排序,所用到的图必须是有向无环

 

DFS代码实现

package myDFS; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Scanner; public class DFS { private int nodeNum;//图的节点个数
    private int[][] graph;//邻接矩阵存储图信息
    private List<Edge> edges = new ArrayList<>();//存储边信息
    private List<Integer> res = new ArrayList<>();//存放深度遍历的路径
    private List<Integer> topoSort = new ArrayList<>();//存放拓扑排序路径

    public static void main(String[] args) { DFS dfs = new DFS(); dfs.inputInfo(); //输入图的信息
        dfs.creatGraph();   //创建有向图
        dfs.outputGraph();  //打印有向图
        dfs.dfs();          //进行深度遍历
        dfs.outputRes();    //输出结果
 } /** * 用户输入,并创建边集合 */
    public void inputInfo() { Scanner scanner = new Scanner(System.in); System.out.print("请输入节点个数:"); nodeNum = scanner.nextInt(); int size = nodeNum; System.out.println("请依次输入边,格式为:起始节点 终止节点(-1 -1作为结束符)"); while (true) { int node1 = scanner.nextInt(); int node2 = scanner.nextInt(); if (node1 < 0 || node2 < 0) break; edges.add(new Edge(node1, node2)); } } /** * 创建有图 */
    public void creatGraph() { graph = new int[nodeNum + 1][nodeNum + 1];//因为节点从1开始编号,所以加一
        for (int i = 0; i < edges.size(); i++) { graph[edges.get(i).node1][edges.get(i).node2] = 1; } } /** * 深度遍历递归调用 */
    static int time = 0; public void dfs() { // visited访问标志符数组,有三种状态: // visited=0表示节点未被发现 // visited=1表示节点已经被发现,但没有完成深搜 // visited=2表示节点已经深搜完毕
        int visited[] = new int[nodeNum + 1]; int startTime[] = new int[nodeNum + 1];//用于记录各节点被发现的时间
        int endTime[] = new int[nodeNum + 1];//用于记录各节点完成深搜的时间
        time = 0;//全局时钟 //选择开始节点进行深搜
        for (int i = 1; i <= nodeNum; i++) { if (visited[i] == 0) { dfs(graph, i, visited, startTime, endTime); } } System.out.println("startT:" + Arrays.toString(startTime)); System.out.println("endT:" + Arrays.toString(endTime)); } /** * 递归实现深度遍历 * * @param graph 有向图 * @param node 当前节点 * @param visited 访问标识符数组 * @param startT 开始时间数组 * @param endT 结束时间数组 */
    public void dfs(int graph[][], int node, int visited[], int startT[], int endT[]) { res.add(node);//记录访问路径
        time++;       //节点被发现,更新时钟
        visited[node] = 1; startT[node] = time; while (findNext(graph, node, visited) != -1) { int nestNode = findNext(graph, node, visited); if (visited[nestNode] == 0) { dfs(graph, nestNode, visited, startT, endT); } } time++;//节点深搜结束,更新时钟
        endT[node] = time; visited[node] = 2; topoSort.add(node); } /** * 获取当前节点的下一个节点 * * @param graph 有向图 * @param node 当前节点 * @param visited 访问标志符数组 * @return -1表示没有下一个节点 */
    private int findNext(int[][] graph, int node, int visited[]) { for (int i = 0; i < edges.size(); i++) { Edge edge = edges.get(i); if (edge.node1 == node && visited[edge.node2] == 0) { return edge.node2; } } return -1; } /** * 打印有向图 */
    public void outputGraph() { for (int i = 1; i <= nodeNum; i++) { for (int j = 1; j <= nodeNum; j++) { System.out.print(graph[i][j] + " "); } System.out.println(); } } /** * 输出深度遍历的路径以及拓扑排序路径 */
    public void outputRes() { System.out.println("访问路径" + res); System.out.println("拓扑路径" + topoSort); } } class Edge { int node1; int node2; int weight; public Edge(int node1, int node2) { this.node1 = node1; this.node2 = node2; } }

 

测试用例:

6

1 2 
1 3 
1 4 
2 3 
2 5 
3 4 
3 5 
4 6 
5 6 
-1 -1 

输出:

0 1 1 1 0 0
0 0 1 0 1 0
0 0 0 1 1 0
0 0 0 0 0 1
0 0 0 0 0 1
0 0 0 0 0 0
startT:[0, 1, 2, 3, 4, 8, 5]
endT:[0, 12, 11, 10, 7, 9, 6]
访问路径[1, 2, 3, 4, 6, 5]
拓扑路径[6, 4, 5, 3, 2, 1]

 

参考资料

  1. 《算法导论》深度优先搜索
  2. 什么是拓扑排序
  3. 拓扑排序及其Java实现

 

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM