数据结构——图(3)

拓扑排序

  • 有向无环图:无环,不存在循环
  • 拓扑排序满足:每一顶点都不会通过边指向前驱顶点
  • 拓扑排序实质:由某一个集合的偏序,得到它的全序(△全序,即任两个成员均可比较)

算法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):

拓扑排序 - GeeksforGeeks

对图做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博客 

图解值得参考:图解:什么是关键路径?-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);
		}
	}
}

  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值