iltonmi's docs

拓扑排序

目录

    资料来源于The algorithm design manual

    拓扑排序是所有日程安排问题的自然解法。

    拓扑排序的使用条件是有向无环图,即DAG。

    在日程安排正常完成的情况下,日程之间是没有循环依赖的,因此日程安排问题天然适合使用拓扑排序解决。

    拓扑排序有2种解法。

    第一个算法Kahn:

    原文描述:

    The conceptually simplest linear-time algorithm for topological sorting performs

    a depth-first search of the DAG to identify the complete set of source vertices, where

    source vertices are vertices of in-degree zero. At least one such source must exist in

    any DAG. Source vertices can appear at the start of any schedule without violating

    any constraints. Deleting all the outgoing edges of these source vertices will create

    new source vertices, which can then sit comfortably to the immediate right of the

    first set. We repeat until all vertices are accounted for. A modest amount of care

    with data structures (adjacency lists and queues) is sufficient to make this run in

    O(n + m) time.

    大意如下:

    1. 源顶点为入度为0的顶点
    2. 在不违反任何限制的情况下,源顶点可以出现在任何一种可能的日程中
    3. 删除将源顶点为起点的边会产生新的源顶点,新的源顶点可以出现在被删除的源顶点后面。
    4. 反复删除源顶点,直到所有顶点都被考虑。
    1. 生成邻接矩阵同时使用数组记录所有顶点的入度
    2. 所有入度为0的顶点入队,并打印
    3. 若队列非空,取出队首元素;若队列为空,结束。
    4. 将以队首元素为起点的边的终点的入度自减1,然后若终点的入度变为0则其入队。跳到第3步。
    

    第二个算法DFS:

    顶点V涉及3种状态:undiscovered, discovered, processed

    从DFS角度解释这3种状态:

    1. undiscovered,代表没有在DFS函数中被发现,未曾成为DFS的起点,即从未成为DFS的参数。
    2. discovered,代表成为了DFS的起点,但是搜索并未结束。
    3. processed,代表成为了DFS的起点,并且搜索结束。

    Topological sorting can be performed efficiently using depth-first searching. A

    directed graph is a DAG if and only if no back edges are encountered. Labeling

    the vertices in the reverse order that they are marked processed finds a topological

    sort of a DAG. Why? Consider what happens to each directed edge {x,y} as we

    encounter it exploring vertex x:

    If y is currently undiscovered, then we start a DFS of y before we can continue with x. Thus y is marked completed before x is, and x appears before y in the topological order, as it must.

    If y is discovered but not completed, then {x,y} is a back edge, which is forbidden in a DAG.

    If y is processed, then it will have been so labeled before x. Therefore, x appears before y in the topological order, as it must.

    ...

    We push each vertex on a stack as soon as we have evaluated all outgoing edges.

    The top vertex on the stack always has no incoming edges from any vertex on the

    stack. Repeatedly popping them off yields a topological ordering.

    大意如下:

    以顶点被标记为processed的相反顺序标识顶点,能够发现DAG的拓扑序。理由如下:
    
    当正在DFS顶点x时,对于每条有向边{x, y}:
    
    1. 若y是undiscovered,则以y为起点启动DFS。因此,y会率先完成(译者注:即被标记为processed)。当然,对于拓扑顺序,x在y之前。

    2. 若y是discovered但未完成,则存在循环依赖,此图不是DAG。

    3. 若y是processed,则y会先于x被标识(!!!)。当然,对于拓扑顺序,x在y之前。(y点是殊途同归的交汇点)

      当考虑完某个顶点所有的出边后,将顶点放入栈中。(即DFS后,放入栈中。)

      注意:这样做利用栈达到了逆序的目的。

      最后栈顶的元素必然入度为0,将栈中元素逐个弹出,即为拓扑序。

    topsort(graph *g)
    {
        int i; /* counter */
        init_stack(&sorted);
        for (i=1; i<=g->nvertices; i++)
            if (discovered[i] == FALSE)
                dfs(g,i);
        print_stack(&sorted); /* report topological order */
    }
    DFS(G,u)
        state[u] = “discovered”
        process vertex u if desired
        entry[u] = time
        time = time + 1
        for each v ∈ Adj[u] do
            process edge (u,v) if desired
            if state[v] = “undiscovered” then
                p[v] = u
                DFS(G,v)
        state[u] = “processed”
        exit[u] = time
        time = time + 1
    

    DFS还有另一种实现:

    思路:将邻接矩阵转换为逆邻接矩阵,即逆转图的所有边的方向。

    当DFS顶点x时,对每条有向边{x, y}。
    
    1. 若y是undiscovered,则以y为起点启动DFS。最后打印x。当然,对于拓扑顺序,y在x之前(这是和上一种方法不同的地方)。
    2. 若y是discovered但未完成,则存在循环依赖,此图不是DAG。
    3. 若y是processed,则y会先于x被标识(!!!)。当然,对于拓扑顺序,y在x之前(这是和上一种方法不同的地方)。

    思路理解:逆转了边的方向,因此所有y都是x的前置事件,因此不需要用栈保存事件。

    posted on 2021-01-26 00:38  iltonmi  阅读(73)  评论(0编辑  收藏  举报

    导航