关键路径(算法笔记)

这篇具有很好参考价值的文章主要介绍了关键路径(算法笔记)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文内容基于《算法笔记》和官方配套练题网站“晴问算法”,是我作为小白的学习记录,如有错误还请体谅,可以留下您的宝贵意见,不胜感激。


一、AOV网和AOE网

顶点活动(AOV)网: 顶点表示活动,边集表示活动间的优先关系;AOV网常用来表示活动间的优先关系;
边活动(AOE)网: 顶点表示事件,边集(带权)表示活动,活动一般用时间表示,而事件可以比作中介状态(状态转移->动态规划),当旧活动都结束后才会激活事件以进行新活动;AOE网常用来表示工程的进行过程。
AOV网和AOE网都是有向无环图,且AOV网可以通过将顶点拆成两个顶点,分别表示活动的起点和终点,将两个顶点之间的边表示活动,给定边权即可转化为AOE网。
源点:入度为0的点;
汇点:出度为0的点;
工程的关键要素: 1.工程的持续时间; 2.哪条路径是影响工程持续时间的关键因素(这条路径的变化会引起整个工程时间的变化),即最长路径;
AOE中的最长路径即为关键路径,关键路径上的活动被称为关键活动。
最长路径问题: 寻求图中的最长简单路径(每个顶点只经过一次的路径)。
针对最长路径问题,书中只给出了在有向无环图条件下的求解方式,至于有向有环图和无向图不在讨论范围内。

二、关键路径

求解关键路径实际就是求解有向无环图中(DAG)的最长路径,只是从工程的角度来理解罢了。
求解方式简述:先求点,后夹边
具体求解思路:
关键活动代表了不能拖延的活动,一旦拖延就会引起整个工程进度推迟;
1.设置数组e[]和l[],其中e[r]和l[r]代表活动ar的最早开始时间和最迟开始时间,当e[r]==l[r]时说明活动ar是关键活动,即活动无法推迟。
2.设置数组ve[]和vl[],其中ve[i]和vl[i]分别代表事件i的最早开始时间和最迟开始时间;
对其中的一截活动分析如下:
关键路径,算法笔记,算法,笔记,图论
3.活动的最早开始时间和最迟开始时间受到事件的影响,影响关系具体表现为:
(1)活动ar的最早开始时间等价于事件Vi的最早开始时间,即e[r] = ve[i];
(2)活动ar的最迟开始时间等价于事件Vj的最迟开始时间减去活动ar的持续时间,即l[r] = vl[j] - w[ar];
4.事件的最早开始时间和最迟开始时间受到旧活动和新活动集合的影响,影响关系具体体现为:
(1)旧活动集合最早结束时间等价于新事件的最早开始时间,即关键路径,算法笔记,算法,笔记,图论
(2)新活动集合最迟开始时间等价于旧事件的最迟开始时间,即关键路径,算法笔记,算法,笔记,图论
这样事件集合的最早开始时间和最迟开始时间可以由事件本身和活动持续时间推导出来(状态转移),继而可以求出活动的最早开始时间和最迟开始时间。

从上面的步骤可以得到思考:
1.活动的求解依赖于事件,而事件的求解可以通过旧活动和新活动的集合转化而来,这种活动的集合关系又可以转化为事件本身来表示,这种转化形式就是状态转移,即后面将学习的动态规划;
2.如何理解最短和最长?可以用一句话概述:最短是相对的,而最长是绝对的,旧活动集合的最早结束时间就是旧活动中耗时最长的路径。

三、具体求解

在求解旧活动集合的最早结束时间时,为了保证当前访问的事件的前导事件(旧事件)都已经被访问过,采用拓扑排序来实现当前访问事件的最早开始时间求解。而由于寻找前驱事件的方式不直观(其实在领接表的结构体中多加一个变量保存前驱节点就能实现,相当于双向链表那样的实现方式),所以采用在访问到前驱事件时直接更新后继事件的方式实现对ve[]数组的更新。
而若要求解新活动集合的最迟开始时间,可以采用逆拓扑排序来实现当前访问事件的最迟开始时间求解。通过访问当前事件的后继事件来更新当前事件的方式实现对vl[]数组的更新。逆拓扑序列可以将拓扑序列保存到栈中实现。
需要注意的点:ve[]、vl[]数组的初始化,因为这两个数组的求解都是基于他们本身的不同状态而进行的,所以需要注意这两个数组的边界初始化问题,对于ve[],事件的最早开始时间就是0,而对于vl[],事件的最迟开始时间取决于工程的最早结束时间,即最后一个事件(汇点)的最早开始时间。

若事先没有告知汇点信息,则可以通过求ve[]数组的最大值来确定,因为所有事件的最早开始时间的最大值就是工程的最早结束时间,即汇点。而工程的最早结束时间也就是关键活动所花费的时间(边权之和),即关键路径长度。

四、小题练习

1.关键路径长度
关键路径,算法笔记,算法,笔记,图论
完整代码如下:

//其实队列和栈并不是必须的,这里并没有反复使用队列和栈的特性,用数组足够,明确从哪里开始取即可 
//使用队列只是因为拓扑排序看起来好像和bfs有点像,但其实并不需要先进先出的思想 
//状态转移!!!所以需要注意体会状态的转移过程 
#include<cstdio>
#include<queue>
#include<stack>
#include<vector>
#include<algorithm>
using namespace std;

const int MAXN = 100;
struct Node{
	int v , w;
};
vector <Node> Adj[MAXN];
int ve[MAXN] = {} , vl[MAXN] = {};
stack <int> topOrder;  //记录拓扑排序 
int inDegree[MAXN];    //记录顶点入度 
int n , m;
int MAX = 0;   //记录ve数组最大值,确定汇点 
int MAXI = 0;

bool topological_Sort(){
	queue <int> q;         //记录入度为 0的点 
	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[u],但访问前驱节点并不容易实现 
				ve[v] = ve[u] + Adj[u][i].w; //也可以在领接表的结构体中开一个记录前驱结点的变量 
			if(ve[v] > MAX) {
				MAX = ve[v];
				MAXI = v;
			}
		}
	}
	if(topOrder.size() == n) return true;
	else return false;
}

int Critical_Path(){     //关键路径 
	if(!topological_Sort()) return -1;
	fill(vl , vl + n , MAX);
	while(!topOrder.empty()){   //逆拓扑排序 
		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])   //用u的后继节点来更新vl[u] 
				vl[u] = vl[v] - Adj[u][i].w;  //状态转移 
		}
	}
	return ve[MAXI];
}

int main(){
	scanf("%d%d", &n , &m);
	for(int i = 0; i < m; i++){
		int u , v , w;
		scanf("%d%d%d", &u , &v , &w);
		Node uv = {v , w};
		Adj[u].push_back(uv);
		inDegree[v]++; 
	}
	int ans = Critical_Path();
	if(ans == -1) printf("No");
	else {
		printf("Yes\n");
		printf("%d", ans);	
	}
}

2.关键活动
关键路径,算法笔记,算法,笔记,图论
关键活动可以通过遍历图的所有边(活动)来获得,通过边的两个端点(事件i和事件j)来求活动的最早开始时间和最迟开始时间。注意字典序输出,需要事先对领接表的出边信息按边的终点大小升序排序;
完整代码如下:

#include<cstdio>
#include<queue>
#include<stack>
#include<algorithm>
#include<vector>
using namespace std;

const int MAXN = 100;
struct Node{
	int v , w;
};
vector <Node> Adj[MAXN];
int vl[MAXN] = {} , ve[MAXN] = {};
stack <int> topOrder;
int inDegree[MAXN];
int n , m;
int MAX = -1, MAXI = -1;

bool cmp(Node a , Node b){
	return a.v < b.v;
}

bool topological_Sort(){
	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];
				MAXI = v;
			}
 		}
	}
	if(topOrder.size() == n) return true;
	else return false;
} 

void Critical_Path(){
	if(topological_Sort()) printf("Yes\n");
	else {
		printf("No");
		return;
	}
	fill(vl , vl + n , MAX);
	while(!topOrder.empty()){
		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;
		}
	}
	for(int i = 0; i < n; i++) sort(Adj[i].begin() , Adj[i].end() , cmp); //对终点进行排序 
	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) printf("%d %d\n", u , v); 
		}
	}
}

int main(){
	scanf("%d%d", &n , &m);
	for(int i = 0; i < m; i++){
		int u , v , w;
		scanf("%d%d%d", &u , &v , &w);
		Node uv = {v , w};
		Adj[u].push_back(uv);
		inDegree[v]++; 
	}
	Critical_Path();
}

3.关键路径
关键路径,算法笔记,算法,笔记,图论
将遍历得到的关键路径以图的形式保存在一个新的领接表中,最后通过DFS遍历所有关键路径。还是要注意字典序输出;
完整代码如下:文章来源地址https://www.toymoban.com/news/detail-766410.html

//源点不唯一,汇点也不唯一,对于程序求解,不需要注意超级源点或者超级汇点 
#include<cstdio>
#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(){
	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()){
		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;
		}
	}
	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++){
			printf("%d", Path[i]);
			if(i + 1 < Path.size()) printf("->");
		}
		printf("\n");
	}	 
	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(){
	scanf("%d%d", &n , &m);
	for(int i = 0; i < m; i++){
		int u , v , w;
		scanf("%d%d%d", &u , &v , &w);
		Node uv = {v , w};
		Adj[u].push_back(uv);
		inDegree[v]++; 
		tempinDegree[v]++;
	}
	if(!Critical_Path()) printf("No");
	else {
		printf("Yes\n");
		for(int origin = 0; origin < n; origin++) {
			if(tempinDegree[origin] == 0 && ansAdj[origin].size() != 0) DFS(origin);
		}
	}
}

到了这里,关于关键路径(算法笔记)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 每天一道leetcode:1192. 查找集群内的关键连接(图论&困难&tarjan算法)

    力扣数据中心有 n 台服务器,分别按从 0 到 n-1 的方式进行了编号。它们之间以 服务器到服务器 的形式相互连接组成了一个内部集群,连接是无向的。用 connections 表示集群网络, connections[i] = [a, b] 表示服务器 a 和 b 之间形成连接。任何服务器都可以直接或者间接地通过网络

    2024年02月12日
    浏览(33)
  • 图论算法|深度优先搜索理论基础|797.所有可能的路径|广度优先搜索BFS理论基础|200. 岛屿数量

    dfs是可一个方向去搜,不到黄河不回头,直到遇到绝境了,搜不下去了,在换方向(换方向的过程就涉及到了回溯)。 递归和回溯是相辅相成的 https://leetcode.cn/problems/all-paths-from-source-to-target/ 有向无环图(DAG): 有环无向图是指在图中存在至少一个环(Cycle)的无向图。环是

    2024年02月15日
    浏览(46)
  • Tarjan 算法——图论学习笔记

    在图论问题中,我们经常去研究一些连通性问题,比如: 有向图的联通性:传递闭包——Floyd 算法; 有向图连通性的对称性:强联通分量(SCC)——Tarjan 算法缩点; 无向图的联通性:并查集; 无向图的关键边:桥(割边)、边双——Tarjan 算法缩点; 无向图的关键点:割点

    2024年02月02日
    浏览(29)
  • Acwing-基础算法课笔记之搜索与图论

    bellman-ford算法适用于负权边的图,求 1 到 n 的最多经过k条边的最短距离。 如图所示: 1 2 3 dist 0 ∞ infty ∞ ∞ infty ∞ ⇓ Downarrow ⇓ 1 2 3 dist 0 1 ∞ infty ∞ ⇓ Downarrow ⇓ 1 2 3 dist 0 1 2 此过程中出现了串联的结果,所以是错误的,此时需要进行备份操作。 备份操作如下: 为了

    2024年01月20日
    浏览(41)
  • ✔ ★ 算法基础笔记(Acwing)(三)—— 搜索与图论(17道题)【java版本】

    1. 排列数字(3分钟) 每次遍历dfs参数是 遍历的坑位 原题链接 2. n-皇后问题 原题链接 方法 1. 按行遍历(过程中有回溯、剪枝) 思想: 每次递归中,遍历一行的元素,如果可以放皇后,就递归到下一行,下一行中不行了,就返回来,回溯, 方法2. 按每个元素遍历(没有减枝)

    2024年02月05日
    浏览(36)
  • 算法笔记——路径问题

    在引入介绍如何写一个算法的时候,我们先引入一个题作为例子 1137. 第 N 个泰波那契数 - 力扣(LeetCode) 作为刚开始学习算法的我们,看到这个题目的时候,应该想好以下的问题: 我们要 用什么来表示每个位置的数值 ,甚至是返回哪个元素的下标对应的值? 怎么来?——

    2024年02月10日
    浏览(25)
  • 关键点检测SIFT算法笔记

            SIFT(Scale Invariant Feature Transform),尺度不变特征变换。具有旋转不变性、尺度不变性、亮度变化保持不变性,是一种非常稳定的局部特征。在目标检测和特征提取方向占据着重要的地位。         SIFT算法所查找到的关键点是一些很突出,不因光照、仿射变换和噪

    2024年02月16日
    浏览(34)
  • 算法基础复盘笔记Day07【搜索与图论】—— Prim、Kruskal、染色体判定二分图、匈牙利算法

    ❤ 作者主页:欢迎来到我的技术博客😎 ❀ 个人介绍:大家好,本人热衷于 Java后端开发 ,欢迎来交流学习哦!( ̄▽ ̄)~* 🍊 如果文章对您有帮助,记得 关注 、 点赞 、 收藏 、 评论 ⭐️⭐️⭐️ 📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉 1. 题目

    2024年02月02日
    浏览(37)
  • 算法基础复盘笔记Day06【搜索与图论】—— Dijkstra、bellman-ford、spfa、Floyd

    ❤ 作者主页:欢迎来到我的技术博客😎 ❀ 个人介绍:大家好,本人热衷于 Java后端开发 ,欢迎来交流学习哦!( ̄▽ ̄)~* 🍊 如果文章对您有帮助,记得 关注 、 点赞 、 收藏 、 评论 ⭐️⭐️⭐️ 📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉 1. 题目

    2023年04月22日
    浏览(35)
  • 图论(欧拉路径)

    理论: 所有边都经过一次,若欧拉路径,起点终点相同,欧拉回路 有向图欧拉路径:恰好一个out=in+1,一个in=out+1,其余in=out 有向图欧拉回路:所有in=out 无向图欧拉路径:两个点度数奇,其余偶 无向图欧拉回路:全偶 基础练习 P7771 【模板】欧拉路径 P2731 [USACO3.3] 骑马修栅栏

    2024年02月05日
    浏览(23)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包