图的应用 - 拓扑排序

127 阅读3分钟

拓扑排序的概念待完善, 主要以LeetCode207-课程表为例子, 说明下拓扑排序.

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程  bi 。

例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。

示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。

这里我用java来实现的.

这个解法耗时相当长...当时击败了5.77%的用户┭┮﹏┭┮, 贴在这里主要是为了复习一遍图的知识:

如何根据边的关系构造邻接表结构的图,

如何寻找邻接表的入度,

邻接表结构的拓扑排序如何实现.

class Solution {
    // 邻接表的定义
    static class Graph {
        int vertexNum;
        int edgeNum;
        VexNode[] vertexLinkList;
    }
    static class VexNode {
        int node;
        AdjNode firstPoint;
    }
    static class AdjNode {
        int index;
        AdjNode adjNode;
    }
    // 判断是否可能完成所有课程的学习
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        if (prerequisites.length == 0) return true;
        return checkSelfLoop(prerequisites) && checkSimpleGraph(prerequisites);
    }

    /**
     * 检查是否有自有环(自己指向自己)
     *
     * @param edge 边信息
     * @return 自有环-false, 非自有环-true
     */
    public boolean checkSelfLoop(int[][] edge) {
        for (int[] ints : edge) {
            if (ints[0] == ints[1]) {
                return false;
            }
        }
        return true;
    }

    /**
     * 检查简单图或平行图是否有环
     *
     * @param edgeInfo  边信息
     * @return 有环-false, 无环-true
     */
    public boolean checkSimpleGraph(int[][] edgeInfo) {
        Graph graph = createALGraph(edgeInfo);
        int num = topologicalSorting(graph);
        return num == graph.vertexNum;
    }

    /**
     * 邻接表的拓扑排序
     *
     * @param graph 图
     * @return 拓扑序列的顶点个数
     */
    public int topologicalSorting(Graph graph) {
        int topologicalSeqNum = 0;
        int[] inDegrees = findInDegrees(graph);
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < graph.vertexNum; ++i) {
            if (inDegrees[i] == 0) {
                stack.push(graph.vertexLinkList[i].node);
            }
        }
        while (!stack.empty()) {
            int nodeIndex = searchALGVexIndex(graph, stack.pop());
            topologicalSeqNum++;
            AdjNode tempP = graph.vertexLinkList[nodeIndex].firstPoint;
            while (tempP != null) {
                inDegrees[tempP.index]--;
                if (inDegrees[tempP.index] == 0) {
                    stack.push(graph.vertexLinkList[tempP.index].node);
                }
                tempP = tempP.adjNode;
            }
        }
        return topologicalSeqNum;
    }

    /**
     * 找入度
     *
     * @param graph 逆邻接表图
     * @return 入度数组
     */
    public int[] findInDegrees(Graph graph) {
        int i;
        int[] inDegrees = new int[graph.vertexNum];
        for (i = 0; i < graph.vertexNum; ++i) {
            AdjNode tempPoint = graph.vertexLinkList[i].firstPoint;
            while (tempPoint != null) {
                inDegrees[tempPoint.index]++;
                tempPoint = tempPoint.adjNode;
            }
        }
        return inDegrees;
    }

    /**
     * 创建邻接表
     *
     * @param prerequisites 边信息
     * @return 邻接表的图
     */
    public Graph createALGraph(int[][] prerequisites) {
        int i;
        Graph graph = new Graph();
        graph.edgeNum = prerequisites.length;
        // 根据边信息获取部分顶点
        int[] vertexArray = sortAndDistinct(prerequisites);
        graph.vertexNum = vertexArray.length;
        // 初始化邻接表
        graph.vertexLinkList = new VexNode[graph.vertexNum];
        for (i = 0; i < graph.vertexNum; i++) {
            graph.vertexLinkList[i] = new VexNode();
        }
        for (i = 0; i < vertexArray.length; ++i) {
            graph.vertexLinkList[i].node = vertexArray[i];
            graph.vertexLinkList[i].firstPoint = null;
        }
        // 创建邻接表
        for (i = 0; i < graph.edgeNum; ++i) {
            int tailIndex = searchALGVexIndex(graph, prerequisites[i][1]);
            int headIndex = searchALGVexIndex(graph, prerequisites[i][0]);
            if (tailIndex != -1 && headIndex != -1) {
                createNodeList(graph, tailIndex, headIndex);
            } else {
                // 抛异常, 该数据未在顶点集里找到
                return null;
            }
        }
        return graph;
    }

    /**
     * 创建邻接表的结点表
     *
     * @param graph     图
     * @param tailIndex 尾指针
     * @param headIndex 头指针
     */
    private void createNodeList(Graph graph, int tailIndex, int headIndex) {
        AdjNode adjNode = new AdjNode();
        adjNode.index = headIndex;
        adjNode.adjNode = graph.vertexLinkList[tailIndex].firstPoint;
        graph.vertexLinkList[tailIndex].firstPoint = adjNode;
    }

    /**
     * 根据边的关系推出部分(或全部)有关系的顶点
     * 并非所有顶点都有关系, 只需要找出有关系的顶点
     *
     * @param edge 边信息
     * @return 有关系的顶点信息
     */
    public int[] sortAndDistinct(int[][] edge) {
        int i;
        int j = 0;
        int tempIndex = 0;
        int[] distinctArray = new int[edge.length << 1];
        for (i = 0; i < edge.length; ++i) {
            distinctArray[j++] = edge[i][0];
            distinctArray[j++] = edge[i][1];
        }
        Arrays.sort(distinctArray);
        for (i = 0; i < edge.length << 1; ++i) {
            if (distinctArray[tempIndex] != distinctArray[i]) {
                distinctArray[++tempIndex] = distinctArray[i];
            }
        }
        int[] resultArray = new int[tempIndex + 1];
        for (i = 0; i <= tempIndex; ++i) {
            resultArray[i] = distinctArray[i];
        }
        return resultArray;
    }

    /**
     * 寻找给定结点的下标
     *
     * @param graph    图
     * @param nodeData 结点数据
     * @return 下标
     */
    public int searchALGVexIndex(Graph graph, int nodeData) {
        for (int i = 0; i < graph.vertexNum; ++i) {
            if (nodeData == graph.vertexLinkList[i].node) {
                return i;
            }
        }
        return -1;
    }
}