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

深度遍历算法描述

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

/*
注解:
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实现

 

 

posted @ 2021-09-20 10:37  云墨亦白  阅读(1055)  评论(0)    收藏  举报