【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
之前我们介绍过,在一个工程中我们关心两个问题:
(1)工程是否顺利进行
(2)整个工程最短时间。
之前我们优先关心的是顶点(AOV),同样我们也可以优先关心边(同理有AOE)。(Activity On Edge Network)
看看百度百科上解释:
AOE网:Activity on edge network
若在带权的有向图中,以顶点表示事件,以有向边表示活动,边上的权值表示活动的开销(如该活动持续的时间),则此带权的有向图称为AOE网。
如果用AOE网来表示一项工程,那么,仅仅考虑各个子工程之间的优先关系还不够,更多的是关心整个工程完成的最短时间是多少;
哪些活动的延期将会影响整个工程的进度,而加速这些活动是否会提高整个工程的效率。
因此,通常在AOE网中列出完成预定工程计划所需要进行的活动,每个活动计划完成的时间,要发生哪些事件以及这些事件与活动之间的关系,
从而可以确定该项工程是否可行,估算工程完成的时间以及确定哪些活动是影响工程进度的关键。
很显然,顶点表示事件,边表示活动,边的权则表示活动持续时间。
AOE一般用来估算工程的完成时间。
AOE表示工程的流程,把没有入边的称为始点或者源点,没有出边的顶点称为终点或者汇点。一般情况下,工程只有一个开始,一个结束,
所以正常情况下,AOE只有一个源点一个汇点。
AOV和AOE的区别:
1.AOV用顶点表示活动的网,描述活动之间的制约关系。
2.AOE用边表示活动的网,边上的权值表示活动持续的时间。
AOE 是建立在子过程之间的制约关系没有矛盾的基础之上,再来分析整个过程需要的时间。
AOE研究:
a.完成整个过程至少需要多长时间。
b.哪些活动影响工程的进度?
关键路径:从源点到汇点具有最大长度的路径。这个概念要清楚,一个工程不一定有一条关键路径,可能会有多条。
关键活动:关键路径上的活动(边)。
针对上面AOE所关心的问题,要想缩短工程时间,就缩短关键路径上的过程即可。(缩短后可能出现之前的关键路径变成了非关键路径)
由于AOE网上所有的活动是可以并行进行。这里举一个例子,组装一个变形金刚,需要头,左膀右臂,身体,左腿右腿。
我们可以有两种方法:1.先做好头,做左手臂,做右手臂,做身体,做左腿,做右腿,然后再组装。
2.同时做头、手臂、身体、腿的部分,每有一个头、两个手臂、两个腿和一个身体的时候,就可以组装了。
方法1.如我们计算机中的串行运行。这样时间开销是累加的。
方法2.如我们计算机中的并行运行。这样时间开销可以立体应用。在此方法中,同时做各个部位时间不同,比如做头时间最长,那么整个一个
变形金刚所用的时间决定与做头的时间,如果做手臂的时间是做头时间的一半,那么就是说做手臂的时间点可以在头做了一半的时候。只要不超过这个
时间点,手臂部分是不会影响整个工程的进度的。
这里定义四个定义:前两个针对顶点,后两个针对边
事件最早开始时间:顶点Vi最早发生的时间。
事件最晚开始时间:顶点Vi最晚发生的时间,超出则会延误整个工期。
活动的最早开始时间:边Eg最早发生时间。
活动的最晚开始时间:边Eg最晚发生时间。不推迟工期的最晚开工时间。
下面这个例子说明一下:

说明:上图中J中为49,是最早开始时间。这里可以看到最早开始时间,就是完要成该顶点,前面的所有到该点的路径都要已经完成。所以取路径最大那一条。
补充说明:
事件最早开始时间:例子图中,F点,ACF(9) 和 ADF(19),到达F点时候,保证AC和AD都完成,这样 F才能开始,所以F点的最早开始时间取最大值,即19.
可以看出,要求出到某一点的最早开始时间,则需要将汇于该点的所有路径的时间求出来,取最大值。
事件最迟开始时间:这里是反着推,比如H点最迟开始时间,H到J 与 H到I到J两条路径,39 和 44,所谓最迟开始时间,就是超过这个时间就会影响整个工程进度,
而这个时间是时间点,是从源点工程开始计时的,所以对于H点,39和44是相对于源点,如果取44,则H-J这条路径就会拖延,最迟开始时间选择最小值。
关键路径的特点:我们寻找关键路径——关键路径就是关键活动(顶点与顶点之间的边组成),就是我们怎么判断该顶点是否为关键活动(边)的顶点,即判断边是否为关键活动。
前面定义过,关键路径就是图中从源点到汇点最长(权值最大)的路径。
这条路径就决定了整个工程的工期,这说明一个什么问题?
关键路径上的顶点与顶点之间的活动的应该最早开始和最迟开始时间是相等的,
如果不等那么说明活动还有余额时间(在最早开始时间和最迟开始时间之间可以任选一个时间点开始),这说明还有其他活动是决定这个工程时间的,那就不是关键路径了。
算法思想:
要准备两个数组,a:最早开始时间数组etv,b:最迟开始时间数组。(针对顶点即事件而言)
1.从源点V0出发,令etv[0](源点)=0,按拓扑有序求其余各顶点的最早发生时间etv[i](1 ≤ i ≤ n-1)。同时按照上一章
拓扑排序的方法检测是否有环存在。
2.从汇点Vn出发,令ltv[n-1] = etv[n-1],按拓扑排序求各个其余各顶点的最迟发生时间ltv[i](n-2 ≥ i ≥ 2);
3.根据各顶点的etv和ltv数组的值,求出弧(活动)的最早开工时间和最迟开工时间,求每条弧的最早开工时间和最迟开工时间是否相等,若相等,则是关键活动。
注意:1,2 完成点(事件)的最早和最迟。3根据事件来计算活动最早和最迟,从而求的该弧(活动)是否为关键活动。
关键代码:
1.对图进行拓扑排序,存储了拓扑排序的顺序,作为关键路径的计算最迟开始时间的依据。
int TopplogicalSort(GraphAdjList *g)
{
int count=0;
eNode *e=NULL;
StackType *stack=NULL;
StackType top=0;
stack = (StackType *)malloc((*g).numVextexs*sizeof(StackType));
int i;
//初始化拓扑序列栈
g_topOfStk = 0;
//开辟拓扑序列栈对应的最早开始时间数组
g_etv = (int *)malloc((*g).numVextexs*sizeof(int));
//初始化数组
for (i=0;i<(*g).numVextexs;i++)
{
g_etv[i]=0;
}
//开辟拓扑序列的顶点数组栈
g_StkAfterTop = (int *)malloc(sizeof(int)*(*g).numVextexs);
for (i=0;i<(*g).numVextexs;i++)
{
if (!(*g).adjList[i].numIn)
{
stack[++top] = i;
// printf("init no In is %c\n",g_init_vexs[i]);
}
}
while(top)
{
int geter = stack[top];
top--;
//把拓扑序列保存到拓扑序列栈,为后面做准备
g_StkAfterTop[g_topOfStk++] = geter;
printf("%c -> ",g_init_vexs[(*g).adjList[geter].idx]);
count++;
//获取当前点出度的点,对出度的点的入度减一(当前点要出图)。
//获取当前顶点的出度点表
e = (*g).adjList[geter].fitstedge;
while(e)
{
int eIdx = e->idx;
//选取的出度点的入度减一
int crntIN = --(*g).adjList[eIdx].numIn;
if (crntIN == 0)
{
//如果为0,则说明该顶点没有入度了,是下一轮的输出点。
stack[++top] = eIdx;
// printf("running the vex is %c\n",g_init_vexs[e->idx]);
}
//求出关键路径
if ((g_etv[geter] + e->weigh) > g_etv[eIdx])
{
g_etv[eIdx] = g_etv[geter] + e->weigh;
}
e = e->next;
}
}
if (count < (*g).numVextexs)//如果图本身就是一个大环,或者图中含有环,这样有环的顶点不会进栈而被打印出来。
{
return false;
}
else
{
printf("finish\n");
return true;
}
}
2.关键路径代码:
void CriticalPath(GraphAdjList g)
{
int i;
int geter;
eNode *e = NULL;
g_topOfStk--;
//1.初始化最迟开始时间数组(汇点的最早开始时间(初值))
g_ltv = (int *)malloc(sizeof(int)*g.numVextexs);
for (i=0;i<g.numVextexs;i++)
{
g_ltv[i] = g_etv[g.numVextexs-1];
}
//2.求每个点的最迟开始时间,从汇点到源点推。
while (g_topOfStk)
{
//获取当前出栈(反序)的序号
geter = g_StkAfterTop[g_topOfStk--];
//对每个出度点
if (g.adjList[geter].fitstedge != NULL)
{
e = g.adjList[geter].fitstedge;
while(e != NULL)
{
int eIdx = e->idx;
if (g_ltv[eIdx] - e->weigh < g_ltv[geter])
{
g_ltv[geter] = g_ltv[eIdx] - e->weigh;
}
e = e->next;
}
}
}
int ete,lte;//活动最早开始和最迟开始时间
printf("start:->");
//3.求关键活动,即ltv和etv相等的
for (i=0;i<g.numVextexs;i++)
{
if (g.adjList[i].fitstedge)
{
e = g.adjList[i].fitstedge;
while(e)
{
int eIdx = e->idx;
//活动(i->eIdx)最早开始时间:事件(顶点) i最早开始时间
ete = g_etv[i];
//活动(i->eIdx)最迟开始时间:事件(顶点) eIdx 最迟开始时间 减去 活动持续时间
lte = g_ltv[eIdx] - e->weigh;
if (ete == lte)
{
printf("(%c - %c)->",g_init_vexs[i],g_init_vexs[eIdx]);
}
e= e->next;
}
}
}
printf(" end\n");
}
编程所用的图:

拓扑排序结果:

过程:
1.从J开始,无后继,不做任何事情;
2.G,G的ltv为27,ltv-weight = 27-2 < 27,所以G的ltv为25;
3.I,I的ltv为27,ltv-weight = 27 -3 < 27,所以I的ltv为24;
4.H,H的ltv为24(I的ltv),24-5 < 24,所以H 的ltv为19;
依次类推。。。
完成top排序和关键路径后:
全局存放各个顶点的最早开始和最迟开始时间:

完整代码:
// grp-top.cpp : 定义控制台应用程序的入口点。
//
// grp-top.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdlib.h>
#define MAXVEX 100
#define IFY 65535
typedef char VertexType;
typedef int EdgeType;
typedef int IdxType;
typedef int QueueType;
typedef int StackType;
//-------
int *g_etv = NULL;
int *g_ltv = NULL;
int *g_StkAfterTop;
int g_topOfStk;
///---------------------------------------
//边节点
typedef struct EdgeNode{
IdxType idx;
int weigh;
struct EdgeNode* next;
}eNode;
//顶点节点
typedef struct VexNode{
int numIn; //入度数量
IdxType idx;
eNode *fitstedge;
}vNode;
//图的集合:包含了一个顶点数组
typedef struct {
vNode adjList[MAXVEX];
int numVextexs,numEdges;
}GraphAdjList;
///-----------------------------------
/*VertexType g_init_vexs[MAXVEX] = {'A','B','C','D','E','F','G','H','I','J','K','L'};
char *g_input[] = {
"A->B->C->D",
"B->E",
"C->F->I->J",
"D->E->I->J",
"E",
"F->K",
"G->F->H->K",
"H->I",
"I->J->L",
"J->E->K",
"K->L",
"L"
};*/
///-----------------------------------
VertexType g_init_vexs[MAXVEX] = {'A','B','C','D','E','F','G','H','I','J'};
char *g_input[] = {
"A->B->C",
"B->D->E",
"C->D->F",
"D->E",
"E->G->H",
"F->H",
"G->J",
"H->I",
"I->J",
"J",
NULL
};
char *g_input_weigh[] = {
"3,4",//A
"5,6",//B
"8,7",//C
"3",//D
"9,4",//E
"6",//F
"2",//G
"5",//H
"3",//I
" ",//J
NULL
};
//===============================================================
//队列
//队列节点
typedef struct Node {
QueueType data;
struct Node *next;
}QNode,*qQNode;
//队列指示
typedef struct {
int length;
qQNode frnt,rear;
}spQueue;
void init_Queue(spQueue *Q)
{
(*Q).frnt = NULL;
(*Q).rear = NULL;
(*Q).length = 0;
}
bool isEmptyQueue(spQueue Q)
{
if (Q.length == 0)
{
return true;
}
return false;
}
//进队
void unshiftQueue(spQueue *Q,QueueType elem)
{
//队列空
if (isEmptyQueue(*Q))
{
qQNode n = (qQNode)malloc(sizeof(QNode));
n->data = elem;
n->next = NULL;
(*Q).frnt = n;
(*Q).rear = n;
(*Q).length = 1;
}
else
{
qQNode n = (qQNode)malloc(sizeof(QNode));
n->data = elem;
n->next = NULL;
(*Q).rear->next = n;
(*Q).rear = n;
(*Q).length++;
}
}
//出队
QueueType shiftQueue(spQueue *Q)
{
if (isEmptyQueue(*Q))
{
printf("Warning:Queue is empty!!!\n");
return NULL;
}
if ((*Q).length == 1)
{
QueueType sh = (*Q).frnt->data;
(*Q).frnt = NULL;
(*Q).rear = NULL;
(*Q).length = 0;
return sh;
}
QueueType sh = (*Q).frnt->data;
(*Q).frnt = (*Q).frnt->next;
(*Q).length--;
return sh;
}
//打印队列
void prt_que(spQueue que)
{
if (isEmptyQueue(que))
{
return ;
}
qQNode pos = que.frnt;
while(que.rear->next != pos && pos != NULL)
{
printf(" %d ",pos->data);
pos = pos->next;
}
printf("\n");
}
//===============================================================
///-------
//由节点找节点的序号
IdxType strFindIdx(char ch)
{
int i=0;
VertexType *p = g_init_vexs;
while(p != NULL)
{
if(*p == ch)
{
return i;
}
p++;
i++;
}
return i;
}
//由序号找节点
VertexType idxFindStr(IdxType i)
{
return g_init_vexs[i];
}
void prt_strings(char *p)
{
char *pos = p;
while (NULL != *pos)
{
printf("%c",*pos);
pos++;
}
printf("\n");
}
void prt_strArrays(char *p[],int num)
{
char **pos = p;
int i=0;
while( *pos != NULL && i < num)
{
prt_strings(*pos);
pos++;
i++;
}
}
//自己规定:顶点只能是大写。
bool isVexter(char p)
{
if (p>='A' && p<='Z')
{
return true;
}
return false;
}
bool isNumeric(char p)
{
if (p >= '0' && p <= '9')
{
return true;
}
return false;
}
void init_GrapAdjList(GraphAdjList *g,char **str,char **wstr)
{
char **pos = str;
int cnt=0;
int vcnt = 0;
char **wpos = wstr;//weight value
//入度清零
int i;
for (i=0;i<MAXVEX;i++)
{
(*g).adjList[i].numIn = 0;
}
while (*pos != NULL) //g_input的每行的首指针
{
int i=0;
while(**pos != NULL) //g_input的每行字母
{
if(isVexter(**pos)) //判断是否为顶点(我规定‘A’-‘Z’之间为顶点标志)
{
if (i == 0) //建立顶点的节点
{
(*g).adjList[cnt].idx = strFindIdx(**pos);
(*g).adjList[cnt].fitstedge = NULL;
i=1;
}
else if(i == 1) //建立第一个边的节点
{
eNode* n = (eNode*)malloc(sizeof(eNode));
n->idx = strFindIdx(**pos);
n->next = NULL;
//weight
while (!isNumeric(**wpos))
{
(*wpos)++;
}
n->weigh = **wpos-'0';
(*wpos)++;
(*g).adjList[cnt].fitstedge = n;
i=2;
//添加入度
int iidx = strFindIdx(**pos);
(*g).adjList[iidx].numIn++;
}
else //边节点连接到前一个边节点上
{
eNode* n = (eNode*)malloc(sizeof(eNode));
n->idx = strFindIdx(**pos);
n->next = NULL;
//weight
while (!isNumeric(**wpos))
{
(*wpos)++;
}
n->weigh = **wpos-'0';
(*wpos)++;
//first splist
eNode *r = (*g).adjList[cnt].fitstedge;
while (r->next != NULL)
{
r = r->next;
}
r->next = n;
//添加入度
int iidx = strFindIdx(**pos);
(*g).adjList[iidx].numIn++;
}
}
(*pos)++;
}
wpos++;
cnt++;
pos++;
}
(*g).numVextexs = cnt;
}
int TopplogicalSort(GraphAdjList *g)
{
int count=0;
eNode *e=NULL;
StackType *stack=NULL;
StackType top=0;
stack = (StackType *)malloc((*g).numVextexs*sizeof(StackType));
int i;
//初始化拓扑序列栈
g_topOfStk = 0;
//开辟拓扑序列栈对应的最早开始时间数组
g_etv = (int *)malloc((*g).numVextexs*sizeof(int));
//初始化数组
for (i=0;i<(*g).numVextexs;i++)
{
g_etv[i]=0;
}
//开辟拓扑序列的顶点数组栈
g_StkAfterTop = (int *)malloc(sizeof(int)*(*g).numVextexs);
for (i=0;i<(*g).numVextexs;i++)
{
if (!(*g).adjList[i].numIn)
{
stack[++top] = i;
// printf("init no In is %c\n",g_init_vexs[i]);
}
}
while(top)
{
int geter = stack[top];
top--;
//把拓扑序列保存到拓扑序列栈,为后面做准备
g_StkAfterTop[g_topOfStk++] = geter;
printf("%c -> ",g_init_vexs[(*g).adjList[geter].idx]);
count++;
//获取当前点出度的点,对出度的点的入度减一(当前点要出图)。
//获取当前顶点的出度点表
e = (*g).adjList[geter].fitstedge;
while(e)
{
int eIdx = e->idx;
//选取的出度点的入度减一
int crntIN = --(*g).adjList[eIdx].numIn;
if (crntIN == 0)
{
//如果为0,则说明该顶点没有入度了,是下一轮的输出点。
stack[++top] = eIdx;
// printf("running the vex is %c\n",g_init_vexs[e->idx]);
}
//求出关键路径
if ((g_etv[geter] + e->weigh) > g_etv[eIdx])
{
g_etv[eIdx] = g_etv[geter] + e->weigh;
}
e = e->next;
}
}
if (count < (*g).numVextexs)//如果图本身就是一个大环,或者图中含有环,这样有环的顶点不会进栈而被打印出来。
{
return false;
}
else
{
printf("finish\n");
return true;
}
}
void CriticalPath(GraphAdjList g)
{
int i;
int geter;
eNode *e = NULL;
g_topOfStk--;
//1.初始化最迟开始时间数组(汇点的最早开始时间(初值))
g_ltv = (int *)malloc(sizeof(int)*g.numVextexs);
for (i=0;i<g.numVextexs;i++)
{
g_ltv[i] = g_etv[g.numVextexs-1];
}
//2.求每个点的最迟开始时间,从汇点到源点推。
while (g_topOfStk)
{
//获取当前出栈(反序)的序号
geter = g_StkAfterTop[g_topOfStk--];
//对每个出度点
if (g.adjList[geter].fitstedge != NULL)
{
e = g.adjList[geter].fitstedge;
while(e != NULL)
{
int eIdx = e->idx;
if (g_ltv[eIdx] - e->weigh < g_ltv[geter])
{
g_ltv[geter] = g_ltv[eIdx] - e->weigh;
}
e = e->next;
}
}
}
int ete,lte;//活动最早开始和最迟开始时间
printf("start:->");
//3.求关键活动,即ltv和etv相等的
for (i=0;i<g.numVextexs;i++)
{
if (g.adjList[i].fitstedge)
{
e = g.adjList[i].fitstedge;
while(e)
{
int eIdx = e->idx;
//活动(i->eIdx)最早开始时间:事件(顶点) i最早开始时间
ete = g_etv[i];
//活动(i->eIdx)最迟开始时间:事件(顶点) eIdx 最迟开始时间 减去 活动持续时间
lte = g_ltv[eIdx] - e->weigh;
if (ete == lte)
{
printf("(%c - %c)->",g_init_vexs[i],g_init_vexs[eIdx]);
}
e= e->next;
}
}
}
printf(" end\n");
}
int _tmain(int argc, _TCHAR* argv[])
{
GraphAdjList grp;
printf("print Matix: of Vextexs:\n");
prt_strArrays(g_input,10);
printf("print Matix: of Weigh:\n");
prt_strArrays(g_input_weigh,10);
init_GrapAdjList(&grp,g_input,g_input_weigh);
printf("Top sort:\n");
if (!TopplogicalSort(&grp))
{
printf("grp wrong!\n");
}
CriticalPath(grp);
getchar();
return 0;
}
测试结果:


来源:oschina
链接:https://my.oschina.net/u/3797187/blog/1832903