数据结构应用实例(五)——关键路径
Content:
- 一、问题描述
- 二、算法思想
- 三、代码实现
- 四、小结
一、问题描述
设计实现 AOE 网的关键活动与关键路径问题;
二、算法思想
- 获取拓扑序列;
- 计算节点的最早开始时间 v e [ i ] ve[i] ve[i];
- 计算节点的最晚开始时间 v l [ j ] vl[j] vl[j];
- 查找关键路径
三、代码实现
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define maxx 999999
#pragma warning(disable:4996)typedef struct arc//弧
{ int index;//指向节点编号int weight;//边的权值struct arc *next;
}AR;typedef struct MyGraph
{int type;//0表示无向图,1表示有向图int arcnum,vexnum;//边的个数、顶点个数char **vexname;//存放顶点名称的二维数组 AR *N;//表头数组int **A;//邻接矩阵
}GH;int findvex(char *s,GH *G);//找到图G中名称为s的节点编号,并将其返回
void createGraph(GH *G);//创建图G
void showGraph(GH *G);//以邻接表的形式显示图Gint Topological_sort(GH *G,int *q);//对G进行拓扑排序,q用于存放拓扑序列;如果G中有回路,返回1,否则返回0
void CriticalPath(GH *G);//寻找G中的关键路径
void findCP(int *t,int top,int end,int *visit,GH *G,int *ve,int *vl,int *count);//递归函数,用于在图G中寻找关键路径;
//t为栈,存储关键路径上节点编号;end表示汇点编号;visit表示访问标记数组;ve,vl表示节点的最早开始时间和最晚开始时间;count表示关键路径条数;int main(void)
{GH *G;G=(GH *)malloc(sizeof(GH));createGraph(G);printf("图的邻接表形式:\n");showGraph(G);CriticalPath(G);free(G);return 0;
}int findvex(char *s,GH *G)//找到图G中名称为s的节点编号,并将其返回
{int i;for(i=0;i<G->vexnum;i++){if(strcmp(s,G->vexname[i])==0)return i;}printf("读取文件错误.\n");exit(-1);
}void createGraph(GH *G)//创建图G
{int i,j,n,edge;char filename[]="graph.txt";//存放图的数据文件char str[10],str1[10];FILE *fp;AR *p;fp=fopen(filename,"r");if(!fp){printf("打开文件失败!\n");exit(-1);}fscanf(fp,"%d",&G->type);//读取图的类型G->arcnum=0;fscanf(fp,"%d",&n);//读取结点数量G->vexnum=n;//为动态数组分配空间G->vexname=(char **)malloc(n*sizeof(char *));G->N=(AR *)malloc(n*sizeof(AR));G->A=(int **)malloc(n*sizeof(int *));//对头结点数组和邻接矩阵初始化for (i = 0; i < n; i++){G->N[i].next = NULL;G->A[i] = (int *)malloc(n*sizeof(int));for (j = 0; j < n; j++)G->A[i][j]=maxx;}//读取顶点名称for(i=0;i<n;i++){fscanf(fp,"%s",str);G->vexname[i]=(char *)malloc(strlen(str)*sizeof(char));strcpy(G->vexname[i],str);}//读取边while(!feof(fp)){fscanf(fp,"%s",str);fscanf(fp,"%s",str1);fscanf(fp,"%d",&edge);i=findvex(str,G);j=findvex(str1,G);//邻接表p=(AR *)malloc(sizeof(AR));p->index=j;p->weight=edge;p->next=G->N[i].next;G->N[i].next=p;//邻接矩阵G->A[i][j]=edge;G->arcnum++;//边的个数增加if(G->type==0)//如果是无向图{//邻接表p=(AR *)malloc(sizeof(AR));p->index=i;p->weight=edge;p->next=G->N[j].next;G->N[j].next=p;//邻接矩阵G->A[j][i]=edge;}}fclose(fp);
}void showGraph(GH *G)//以邻接表的形式显示图G
{int i;AR *p;//用于遍历for (i = 0; i < G->vexnum; i++){printf("%s",G->vexname[i]);p=G->N[i].next;while (p){if (G->type == 1)printf("-->");else//无向图没有箭头printf("--");printf("%s(%d)",G->vexname[p->index],p->weight);p=p->next;}printf("\n");}printf("\n");
}int Topological_sort(GH *G,int *q)//对G进行拓扑排序,q用于存放拓扑序列;如果G中有回路,返回1,否则返回0
{int i,n;int *d;int *t,top;int index,count;AR *p;n=G->vexnum;d=(int *)malloc(n*sizeof(int));//统计各个节点的入度t=(int *)malloc(n*sizeof(int));//建立栈,用于存储节点编号top=-1;//初始化,将各个节点的入度设为0for (i = 0; i < n; i++)d[i] = 0;//遍历表头数组,统计各个节点的入度for (i = 0; i < n; i++){p=G->N[i].next;while (p){d[p->index]++;p=p->next;}}//挑选入度为0的点进栈for (i = 0; i < n; i++){if (d[i] == 0){top++;t[top]=i;}}count=0;//统计弹出的节点个数while (top >= 0)//若栈非空,弹栈{index=t[top];//栈顶元素编号//栈顶元素不输出//printf("%s ",G->vexname[index]);top--;//记录弹出序列,即拓扑序列q[count]=index;count++;//遍历弹出节点的邻接表,其相邻点的入度减一p=G->N[index].next;while (p){d[p->index]--;if (d[p->index] == 0)//若入度变为0,进栈{top++;t[top]=p->index;}p=p->next;}}//printf("\n");//输出free(t);free(d);if (count == n)//拓扑序列中含有G中全部点,表示没有回路return 0;elsereturn 1;
}void CriticalPath(GH *G)//寻找G中的关键路径
{int i,n;int x,y;//计数器int length;//关键路径长度int num,count;//关键节点个数和关键路径条数AR *p;int *q,*ve,*vl;int *t;//用于在递归寻找关键路径时存储路径int *visit;//访问标记数组n=G->vexnum;q=(int *)malloc(n*sizeof(int));//拓扑序列,存储节点编号ve=(int *)malloc(n*sizeof(int));//最早开始时间vl=(int *)malloc(n*sizeof(int));//最晚开始时间if (Topological_sort(G,q))//获取拓扑序列{printf("该有向图中存在回路,故不存在关键路径.\n");return;}//1.计算最早开始时间//初始化,全设为0for (i = 0; i < n; i++)ve[i] = 0;for (i = 0; i < n; i++)//利用正向拓扑序列计算最早开始时间{x = q[i];p = G->N[x].next;//利用邻接表寻找x的直接后继while (p)//更新x的直接后继的最早开始时间{if (ve[p->index] < ve[x] + (p->weight))ve[p->index] = ve[x] + (p->weight);p = p->next;}}//2.计算最晚开始时间length = ve[q[n - 1]];//关键路径长度//初始化for (i = 0; i < n; i++)vl[i] = length;for (i = n - 1; i >= 0; i--)//利用逆向拓扑序列计算最晚开始时间{y = q[i];//利用邻接矩阵寻找y的直接前驱for (x = 0; x < n; x++)//更新y的直接前驱的最晚开始时间{if (G->A[x][y] < maxx)//找到之后{if (vl[x] > vl[y] - G->A[x][y])vl[x] = vl[y] - G->A[x][y];}}}//3.输出最早开始时间和最晚开始时间num=0;visit=(int *)malloc(n*sizeof(int));printf("节点名称 最早开始时间 最晚开始时间\n");for (i = 0; i < n; i++){x = q[i];//节点编号printf("%4s %10d %12d\n",G->vexname[x],ve[x],vl[x]);if (ve[x] == vl[x]){visit[x] = 0;//关键节点设置为未访问num++;}elsevisit[x]=1;//事先标记非关键节点,避免后续访问}//4.利用递归在关键节点中探寻关键路径//初始化t = (int *)malloc(num*sizeof(int));//存储关键路径中节点编号visit[q[0]]=1;t[0] = q[0];count = 0;//关键路径条数//调用递归函数findCP(t,0,q[n-1],visit,G,ve,vl,&count);printf("\n");free(ve);free(vl);free(q); free(t);free(visit);
}//t为栈,存储关键路径上节点编号;end表示汇点编号;visit表示访问标记数组;ve,vl表示节点的最早开始时间和最晚开始时间;count表示关键路径条数;
void findCP(int *t,int top,int end,int *visit,GH *G,int *ve,int *vl,int *count)//递归函数,用于在图G中寻找关键路径;
{int i;int cur;AR *p;cur=t[top];if (cur == end)//基准情况:到达汇点,输出路径{(*count)++; printf("\n第%d条关键路径:\n",(*count));for (i = 0; i < top; i++)printf("%s-->",G->vexname[t[i]]); printf("%s\n",G->vexname[cur]);}else//非基准情况{p=G->N[cur].next;while (p)//遍历当前节点的直接后继{if (visit[p->index]==0 && ve[cur] + (p->weight) == vl[p->index])//关键工序(边)的判别条件,非关键节点的visit[i]==1{visit[p->index]=1;t[top+1]=p->index;//入栈findCP(t,top+1,end,visit,G,ve,vl,count);//调用递归函数visit[p->index]=0;//撤销标记}p=p->next;}}
}
四、小结
1、 为了利用拓扑序列计算最早和最迟开始时间,在进行拓扑排序时,对排序序列进行记录;
2、 对于关键路径不唯一的情况,采用 DFS 寻找关键路径,查找路径之前,标记非关键节点,避免后续访问,从而简化了节点添入路径的条件,提高算法的执行效率;
3、 t 用于存储关键路径中的节点编号,由于关键路径中的节点均是关键节点,所以在给 t 分配空间时,分配的空间单位数与关键节点个数相同即可;
4、 在实际计算最早开始时间时,做法并不与算法思想完全一致,具体做法为:先设初值, v e [ i ] ve[i] ve[i] 全部为0;然后遍历正向拓扑序列 q q q,对于编号为 q [ i ] q[i] q[i] 的节点,更新其直接后继 y y y 号节点的最早开始时间,即若 v e [ y ] < v e [ q [ i ] ] + A [ q [ i ] ] [ y ] ve[y]<ve[q[i]]+A[q[i]][y] ve[y]<ve[q[i]]+A[q[i]][y],则令 v e [ y ] = v e [ q [ i ] ] + A [ q [ i ] ] [ y ] ve[y]= ve[q[i]]+ A[q[i]][y] ve[y]=ve[q[i]]+A[q[i]][y];对于最迟开始时间,进行类似操作;
相关文章:
数据结构应用实例(五)——关键路径
Content: 一、问题描述二、算法思想三、代码实现四、小结 一、问题描述 设计实现 AOE 网的关键活动与关键路径问题; 二、算法思想 获取拓扑序列;计算节点的最早开始时间 v e [ i ] ve[i] ve[i];计算节点的最晚开始时间 v l [ j ] vl[j] v…...
组播 2024 9 11
PIM(Protocol Independent Multicast)是一种常用的组播路由协议,其独立于底层的单播路由协议,能够在多种网络环境中有效地实现多播路由功能。PIM主要有两种模式:PIM Sparse Mode (PIM-SM) 和 PIM Dense Mode (PIM-DM)&…...
揭秘开发者的效率倍增器:编程工具的选择与应用
文章目录 每日一句正能量前言工具介绍功能特点:使用场景:提高工作效率的方式: 效率对比未来趋势后记 每日一句正能量 这推开心窗之人,可以是亲朋好友,也可以是陌客路人,可以是德高望重的哲人名流࿰…...
在AI的时代,程序员如何才不被淘汰
随着人工智能技术的迅猛发展,大模型(Large Language Models, LLMs)正逐渐成为IT行业的热点。对于程序员来说,转行大模型领域不仅意味着新的机遇,也面临着诸多挑战。本文将探讨程序员转行大模型的机遇与挑战,…...
tabBar设置底部菜单选项以及iconfont图标,setTabBar设置TabBar和下拉刷新API
tabBartabBar属性:设置底部 tab 的表现 首先在pages.json页面写一个tabBar对象,里面放入list对象数组,里面至少要有2个、最多5个 tab, 如果只有一个tab的话,H5(浏览器)依然可以显示底部有一个导航栏,如果没有,需要重启后才有,小程序则报错,只有2个以上才可以…...
2024C题prompt
题目 我正在进行数学建模,需要你为我提供帮助。下面我会将赛题背景和问题发送给你,请你为我提供比赛思路和指导。 以下是赛题背景和赛题说明,不是问题: 农作物的种植策略 根据乡村的实际情况,充分利用有限的耕地资源,…...
Numpy中数组的形状处理
目录 将多维数组降为一维数组竖直方向或水平方向数组的堆叠 数组形状处理的手段主要有reshape,resize,ravel,flatten,vstack,hstack,row_stack,column_stack,下面通过简单 的案例来解释这些方法…...
【动态规划】子序列问题二(数组中不连续的一段)
子序列问题二 1.最长定差子序列2.最长的斐波那契子序列的长度3.最长等差数列4.等差数列划分 II - 子序列 点赞👍👍收藏🌟🌟关注💖💖 你的支持是对我最大的鼓励,我们一起努力吧!😃&am…...
可视耳勺方便吗?可视耳勺热销第一名品牌!
在生活中,耳部清洁是我们常常会关注却又容易忽视细节的一项日常护理。传统挖耳勺有着不可视的局限性,只能凭感觉和经验反复刮蹭耳朵,很容易将耳垢越捅越深,而且还会刮伤耳道。因此,可视耳勺应运而生,它通过…...
micropython 3-wire spi 9bit 写入的问题
网上猛找把,没有,找不到,mpy不愧是没朋友的缩写,没有咋办,自己造! 此库特别适用那些rgb屏的初始化,大多用3线spi,好家伙rgb用了十多个引脚现在想起来省引脚了是吧,就差这…...
导致JVM内存泄露的ThreadLocal详解
1. ThreadLocal介绍 1.1 什么是ThreadLocal Java官方文档中的描述:ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来…...
windows下关闭解除占用端口的进程
环境:windows 10 场景:启动某一应用程序时,提示其他应用已占用此端口,比如端口2425。 解决步骤: 1/3、打开windows的命令提示符,输入以下命令,查找占用此端口2425的PID号: # win…...
Baumer工业相机堡盟工业相机如何通过NEOAPI SDK获取相机当前数据吞吐量(Python)
Baumer工业相机堡盟工业相机如何通过NEOAPI SDK里函数来获取相机当前数据吞吐量(Python) Baumer工业相机Baumer工业相机的数据吞吐量的技术背景CameraExplorer如何查看相机吞吐量信息在NEOAPI SDK里通过函数获取相机接口吞吐量 Baumer工业相机通过NEOAPI…...
版权与开源协议:一场创新与自由的边界之争
在数字时代的浪潮中,版权与开源协议作为知识产权领域的两大支柱,既相互依存又暗自较劲,共同绘制着科技创新的宏伟蓝图。本文将带您深入这场创新与自由的边界之争,探讨版权与开源协议之间的微妙关系,以及它们如何共同推…...
学生用的蓝牙耳机推荐有哪些?实测四款实力出众机型!
在当今数字化学习环境中,学生对蓝牙耳机的需求日益增长,无论是在线课程的学习、图书馆的集中阅读还是日常通勤中的音频资料复习,一款性能优异、舒适度高且价格合理的蓝牙耳机对学生来说至关重要,面对市场上琳琅满目的产品选择&…...
MIT6.824 课程-GFS
GFS 原文:https://zhuanlan.zhihu.com/p/113161014 搬运用于参考学习 概述 存储(Storage)是一个非常关键的抽象,用途广泛。 GFS 论文还提到了很多关于容错、备份和一致性的问题。 GFS 本身是 Google 内部一个很成功的实用系统&…...
力扣第200题 岛屿数量
前言 记录一下刷题历程 力扣第200题 岛屿数量 岛屿数量 原题目: 给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平…...
协议头,wireshark,http
目录 协议头 ip头 udp头 mac层 网络工具 telnet wireshark Http 一、HTTP 协议介绍 二、HTTP 协议的工作过程 三、使用抓包工具抓取报文 四、获取到http请求报文: 五、http请求(request) (一)、认识URL 项…...
vscode ssh离线远程连接ubuntu调试
遇见问题: 1 ssh连接上无法启动服务器的虚拟环境; 2 ssh连接上启动服务器的虚拟环境后无法打断点; 对于问题需要参考下面连接安装python和debugy的插件拓展,并且配置json文件link。VSCode - 离线安装扩展python插件教程_vscode…...
Jenkins 通过 Version Number Plugin 自动生成和管理构建的版本号
步骤 1:安装 Version Number Plugin 登录 Jenkins 的管理界面。进入 “Manage Jenkins” -> “Manage Plugins”。在 “Available” 选项卡中搜索 “Version Number Plugin”。选中并安装插件,完成后可能需要重启 Jenkins。 步骤 2:配置…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...
