1. 定义
前置知识
- DAG: Directed Acyclic Graph 有向无环图
- 拓扑序: 像先修课程一样,即任意课程的前置课程都在其前面。
举个例子
在这个图中,1234
或者1324
是拓扑序。而其他的序列不是,即在一个节点出现之前他的所有祖先节点需要出现。
2. 实现
2.1 DFS
任意选节点,先递归各个子节点,在将根节点放入栈中。最后将栈中元素弹出,即可得到一个拓扑序列。相当于二叉树的后序遍历。
由于存在可能有环的情况,所有节点的状态需要三个状态位来表示以防止和遍历过的冲突。
struct Graph {Graph(int vertex_num = 100):maxVertexSz(vertex_num + 1),graph(vertex_num + 1,std::vector<int>( vertex_num + 1, 0)){}enum Status{to_visit,visiting,visited};void add_edge(int u, int v){graph[u][v] = 1;}bool find_edge(int u, int v) const{return graph[u][v];}std::unordered_set<int> getVertexSet() const{std::unordered_set<int> vertexSet;for (int i = 0; i < maxVertexSz; ++i)for (int j = 0; j < maxVertexSz; ++j)if (graph[i][j])vertexSet.insert(i), vertexSet.insert(j);return vertexSet;}void cal_inDeg(std::vector<int> &inDeg){for (int i = 0; i < maxVertexSz; ++i)for (int j = 0; j < maxVertexSz; ++j)if (graph[i][j])inDeg[j]++;}std::vector<std::vector<int>> graph;int maxVertexSz;
};bool topo_sort_dfs(const Graph &p, std::vector<Graph::Status> &stat,std::stack<int> &st,int node) {if (stat[node] == Graph::visited)return true;if (stat[node] == Graph::visiting)return false;stat[node] = Graph::visiting;bool not_cyclic = true;for (int i = 0; i < p.maxVertexSz; ++i) {if (!not_cyclic)return false;if (p.graph[node][i] != 0) {not_cyclic = topo_sort_dfs(p, stat, st, i);}}st.push(node);stat[node] = Graph::visited;
}std::vector<int> topo_dfs(const Graph &p)
{std::vector<int> topo_seq;std::unordered_set<int> vertexSet(std::move(p.getVertexSet()));std::vector<Graph::Status> stat(p.maxVertexSz, Graph::to_visit);std::stack<int> st;bool not_cyclic = true;for (auto node:vertexSet) {if (not_cyclic && stat[node] == Graph::to_visit)not_cyclic = topo_sort_dfs(p, stat, st, node);}if ( not_cyclic ) {while (!st.empty()) {topo_seq.push_back(st.top());st.pop();}}return topo_seq;
}
2.2 Kahn’s
先计算出所有节点的入度,再将入度为0的节点放入队列中。每次从队列中取出一个节点放入拓扑序列,并根据以该节点为入点的边,降低从该节点到达节点的入度。若降低后节点入度为0,则放入队列中。
其实相当于BFS。
注意对比拓扑序列的序列长度,若长度比节点数目少,则说明图中有环。
std::vector<int> topo_sort_kahn(const Graph &p)
{std::vector<int> topo_seq;int sz = p.maxVertexSz;std::vector<int> inDeg(sz, 0);std::unordered_set<int> vertexSet(std::move(p.getVertexSet()));std::queue<int> q;for ( int i = 0; i < sz; ++i) {for ( int j = 0; j < sz; ++j) {if (p.graph[i][j])inDeg[j]++;}}for (int node:vertexSet) {if (vertexSet.count(node) && !inDeg[node])q.push(node);}while (!q.empty()) {int cur = q.front();topo_seq.push_back(cur);q.pop();for (int i = 0; i < sz; ++i ) {if ( p.graph[cur][i] ) {inDeg[i]--;if (!inDeg[i])q.push(i);}}}return topo_seq;
}
3. 应用
求图中是否有环,AOE中的关键路径等。
4. 参考
OIWIKI
GeekForGeeks