CatCoding

拓扑排序

2013-11-20

最近在看一些图算法,发现拓扑排序频繁出现,这里写一下自己的一些总结。

拓扑排序是对于有向无环图而言的 (DAG),就是对于这个图所有的点 (V1, V2, … Vn) 找到一个点序列使得任意边 (u, v),u 出现在 v 的前面。很容易证明,如果一个有向图中有环那么不存在拓扑排序。

现实中的问题

首先来看现实中哪些问题需要拓扑排序的,课程排序,编译依赖问题,类似的凡是涉及到相关顺序的时间安排,比如 Rails 里面的初始化调用了库Tsort来进行排序。Unix 中有个命令也叫tsort),在有的 makefile 里面还直接使用了这个命令来解决依赖问题。

O(V+E) 的算法

topologicalsorttopologicalsort

拓扑排序的基本算法是用 DFS,我们希望把有出度的点尽量排在前面,所以这里需要注意和 DFS 的区别。比如上面图中的一个 DFS 访问顺序是:5 2 3 1 0 4,但是这不是一个拓扑排序,4 需要排在 0 的前面,5, 4, 0, 2, 3, 1。
拓扑排序中需要等迭代完节点的连接邻点后再把当前点压入栈。


#include <iostream>
#include <stdio.h>
#include <list>
#include <stack>

using namespace std;

class Graph {
    int V;
    list<int>* adj;
    void _topological_sort(int v, bool visited[], stack<int>& stack);
public:
    Graph(int v);
    ~Graph();
    void addEdge(int v, int w);
    void Topological_sort();
};

Graph::Graph(int v):V(v) {
    adj = new list<int>[V];
}

Graph::~Graph() {
    delete [] adj;
}

void Graph::addEdge(int v, int w) {
    adj[v].push_back(w);
}

void Graph::_topological_sort(int v, bool visited[], stack<int>& stack) {
    visited[v] = true;
    for(list<int>::iterator it = adj[v].begin(); it != adj[v].end(); ++it) {
        int u = *it;
        if(visited[u] == false)
            _topological_sort(u, visited, stack);
    }
    stack.push(v);
}

void Graph::Topological_sort() {
    bool visited[V];
    stack<int> stack;
    for(int i=0; i<V; i++)
        visited[i] = false;
    for(int i=V-1; i>=0; i--) {
        if(visited[i] == false) {
            _topological_sort(i, visited, stack);
        }
    }
    while(!stack.empty()) {
        int v = stack.top();
        stack.pop();
        std::cout << " " << v << " ";
    }
    std::cout << std::endl;
}

int main() {
    Graph g(6);
    g.addEdge(5, 2);
    g.addEdge(5, 0);
    g.addEdge(4, 0);
    g.addEdge(4, 1);
    g.addEdge(2, 3);
    g.addEdge(3, 1);
    cout << "Following is topological sort result: \n";
    g.Topological_sort();
    return 0;
}

唯一性

如果一个 DAG 的拓扑排序中任意连续的两点都是可连通的,那么这个序列也就是 DAG 的 Hamiltonian 路径,而且如果 DAG 图的 Hamiltonian 路径存在,那么拓扑排序就是唯一的。否则如果一个拓扑排序结果不是 Hamiltonian 路径,那么就存在多个拓扑排序结果。

其他图算法的预处理

  • DAG 的强连通分支问题
    先得到拓扑排序,形成逆向图 (所有边与原来方向相反),然后根据拓扑排序依次再进行 DFS。

  • DAG 的最短路径问题,这可以在 O(V+E) 复杂度解决最短路径问题。同样类似的算法适用与 DAG 的最长路径问题,给定一个点求 DAG 中的各个点与给定点之间的最长路径。最长路径问题要比最短路径问题难,因为最长路径问题没有最优子结构,对于通用的图的最长路径算法还是 NP 难的问题

公号同步更新,欢迎关注👻