拓扑排序
- 有向无环图:无环,不存在循环
- 拓扑排序满足:每一顶点都不会通过边指向前驱顶点
- 拓扑排序实质:由某一个集合的偏序,得到它的全序(△全序,即任两个成员均可比较)
算法1:
1.在图中选一个无前驱的顶点并输出
2.在图中删除该顶点及其发出的箭头
3.重复上述两步,直到 所有顶点均已输出,或当前图中不存在无前驱顶点。
注意;如果是无向图,条件变为度为1的进入、输出
实现代码:
#include<iostream>
#include<stack>
#include<list>
#include<queue>
using namespace std;
class graph
{
int v;//顶点个数
list<int>* adj;//邻接表
queue<int> visit;//入度点为0进入
int* indegree;//记录入度
public:
graph(int a)
{
v = a;
adj = new list<int>[v];
indegree = new int[v];
for (int i = 0; i < v; i++)
{
indegree[i] = 0;
}
}
void addedge(int v, int w);
bool t_sort();
};
void graph::addedge(int v, int w)
{
adj[v].push_back(w);
indegree[w]++;
}
bool graph::t_sort()//重点部分!!!
{
//找到所有入度点为0的点
for (int i = 0; i < v; i++)
{
if (indegree[i] == 0)
visit.push(i);
}
int count = 0;//记录已输出的数字
while (!visit.empty())
{
int v = visit.front();
visit.pop();
cout << v << " ";
count++;
for (auto beg = adj[v].begin(); beg != adj[v].end(); beg++)
{
if ((--indegree[*beg]) == 0)
visit.push(*beg);
}
}
if (count < v)//未输出所有顶点:图中有回路
return 0;
else
return 1;//排序成功
}
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);
g.t_sort();
return 0;
}
算法2(基于DFS):
对图做DFS,得到一系列DFS树
- 方法:做DFS,每当有顶点被标记为visited,则将其压入栈;一旦发现有后向边则有环。
- 复杂度:O(n+e)
// sorting of a DAG
#include <iostream>
#include<list>
#include<stack>
using namespace std;
// Class to represent a graph
class Graph {
int V;//点的数量
list<int>* adj;//邻接矩阵
void in_Sort(int v, bool visited[],stack<int>& Stack);
public:
Graph(int V);
void addEdge(int v, int w);
// prints a Topological Sort of the complete graph
void Sort();
};
Graph::Graph(int a)
{
V = a;
adj = new list<int>[V];
}
void Graph::addEdge(int v, int w)
{
//v->w的边,把w插到以v为表头的链中
adj[v].push_back(w);
}
// A recursive function used by topologicalSort
void Graph::in_Sort
(int v, bool visited[],stack<int>& Stack)
{
// Mark the current node as visited.
visited[v] = true;
// Recur for all the vertices
// adjacent to this vertex
for (auto i = adj[v].begin(); i != adj[v].end(); ++i)
{
if (!visited[*i])
in_Sort(*i, visited, Stack);
}
//stack储存结果
Stack.push(v);
}
// The function to do Topological Sort.
// It uses recursive topologicalSortUtil()
void Graph::Sort()
{
stack<int> Stack;
// Mark all the vertices as not visited
bool* visited = new bool[V];
for (int i = 0; i < V; i++)
visited[i] = false;
// Call the recursive helper function
// to store Topological
// Sort starting from all
// vertices one by one
for (int i = 0; i < V; i++)
//循环的目的是为了确保遍历每一棵树,找到下一颗树的root
//同一棵树的节点经过topologicalSortUtil后visit全部都变为了1
{
if (visited[i] == false)
in_Sort(i, visited, Stack);
}
// 输出
while (Stack.empty() == false)
{
cout << Stack.top() << " ";
Stack.pop();
}
delete[] visited;
}
// Driver Code
int main()
{
// Create a graph given in the above diagram
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 a Topological Sort of the given "
"graph \n";
// Function Call
g.Sort();
return 0;
}
关键路径
- 工程和有向无环图:工程分解为多个活动;用图来表示前后约束关系。
- AOV图:用顶点表示活动,箭头表示活动间优先关系。
- AOE网:带权的有向无环图,顶点表示事件;边表示活动;权表示活动持续的时间。
- 源点(唯一):入度为0;汇点(唯一):出度为0。
- 关键路径:源点->汇点时间和最长的路径(长度为完成工程的最短时间)
关键活动
- (事件Vi / 事件Vi发出的所有活动)的最早发生时间: V1->Vi的最长路径长度
- e(i):活动ai的最早开始时间
- I(i):最迟开始时间(不影响工程进度)
- 若e(i)=I(i),则为关键活动;关键路径上的活动都是关键活动。
e(i)、I(i)的计算
事件的最早发生时间ve(j),最迟发生时间vl(j)。
假定,活动ai为事件从j到k,dut为活动持续时间。
- e(i)=ve(j) (活动i要想开始至少需要等待的时间)
- I(i)=vl(k)-dut<j,k> (活动i开始之前最多空闲的时间)
求e(i)、I(i),则先求ve(j)、vl(k)
ve(i)、vI(i)的计算
1.ve(i): 从i=0开始向后递推,ve(j)=max{ve(i)+dut(<i, j>)}
所有活动<i,j>完成,j才能开始;所以要找最长的。
2.vl(i):从最后向前递推,vl(i)=min{vl(j)-dut(<i, j>)}
i再晚也不能影响j的启动,所以找j中最早启动的
3.源点和汇点的ve=vl
算法
- Step1:从源点开始计算ve(i),考察指向顶点i的所有边,寻找最大值ve(j)=max{ve(i)+dut(<i, j>)},<i, j>∈E
- Step2:从汇点开始计算vl(i),考察顶点i发出的所有边,寻找最小值vl(i)=min{vl(j)-dut(<i, j>)},<i, j>∈E
- Step3:求每个活动的e(i),等于活动发出顶点的ve值
- Step4:求每个活动的l(i),等于活动指向顶点的vl值减去活动本身的持续时间
- Step5:找到那些l(i)=e(i)的活动,即为关键活动;构成的路径即为关键路径。关键路径可能不止一条。
代码
图解值得参考:图解:什么是关键路径?-CSDN博客
//源点不唯一,汇点也不唯一,对于程序求解,不需要注意超级源点或者超级汇点
#include<iostream>
#include<queue>
#include<stack>
#include<algorithm>
#include<vector>
using namespace std;
const int MAXN = 100;
struct Node {
int v, w;
};
vector <Node> Adj[MAXN];
vector <int> ansAdj[MAXN];
int vl[MAXN] = {}, ve[MAXN] = {};
stack <int> topOrder;
int inDegree[MAXN] = {};
int tempinDegree[MAXN] = {};
int n, m;
int MAX = -1; //记录汇点
bool cmp(int a, int b) {
return a < b;
}
bool topological_Sort() //拓扑排序并得到ve(最早发生时间)
{
queue <int> q;
for (int i = 0; i < n; i++)
if (inDegree[i] == 0) q.push(i);
while (!q.empty())
{
int u = q.front();
q.pop();
topOrder.push(u);
for (int i = 0; i < Adj[u].size(); i++)
{
int v = Adj[u][i].v;
inDegree[v]--;
if (inDegree[v] == 0) q.push(v);
if (ve[u] + Adj[u][i].w > ve[v])
ve[v] = ve[u] + Adj[u][i].w;
if (ve[v] > MAX)
{ //更新汇点
MAX = ve[v];
}
}
}
if (topOrder.size() == n) return true;
else return false;
}
bool Critical_Path() {
if (!topological_Sort()) return false;
fill(vl, vl + n, MAX);
while (!topOrder.empty()) //求vl(最晚发生时间)
{
int u = topOrder.top();
topOrder.pop();
for (int i = 0; i < Adj[u].size(); i++)
{
int v = Adj[u][i].v;
if (vl[v] - Adj[u][i].w < vl[u])
vl[u] = vl[v] - Adj[u][i].w;
}
}
//用vl和ve求e和l
for (int u = 0; u < n; u++)
{ //遍历领接表的所有边,注意不是图的遍历
for (int i = 0; i < Adj[u].size(); i++)
{
int v = Adj[u][i].v, w = Adj[u][i].w;
int e = ve[u], l = vl[v] - w; //活动的最早开始时间和最早结束时间
if (e == l) ansAdj[u].push_back(v); //领接表存储关键路径
}
}
return true;
}
vector <int> Path;//递归输出关键路径
void DFS(int s)
{ //由于只有一个源点和汇点,所以一次DFS即可遍历全部关键路径
Path.push_back(s);
if (Adj[s].size() == 0)
{
for (int i = 0; i < Path.size(); i++)
{
cout << Path[i];
if (i + 1 < Path.size()) cout << "->";
}
cout << endl;
}
sort(ansAdj[s].begin(), ansAdj[s].end(), cmp);//递增
for (int i = 0; i < ansAdj[s].size(); i++)
{
int v = ansAdj[s][i];
DFS(v);
}
Path.pop_back();
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int u, v, w;
cin >> u >> v >> w;
Node uv = { v , w };//终点和权重
Adj[u].push_back(uv);//u的这一条链
inDegree[v]++;
tempinDegree[v]++;
}
if (!Critical_Path()) cout << "No";
else
{
cout << "Yes"<<endl;
for (int origin = 0; origin < n; origin++)
{
//找到源点开始输出
if (tempinDegree[origin] == 0 && ansAdj[origin].size() != 0)
DFS(origin);
}
}
}