一、定義
在一個(gè)表示工程的帶權(quán)有向圖中,用頂點(diǎn)表示事件,用有向邊表示活動(dòng),用邊上的權(quán)值表示活動(dòng)的持續(xù)時(shí)間,這種有向圖的邊表示活動(dòng)的網(wǎng),我們稱之為AOE網(wǎng)(Acitivity On Edge Network)。
AOE網(wǎng)中,沒(méi)有入邊的頂點(diǎn)稱為始點(diǎn)或源點(diǎn)。
AOE網(wǎng)中,沒(méi)有出邊的頂點(diǎn)稱為終點(diǎn)或匯點(diǎn)。
正常情況下,AOE網(wǎng)中,只有一個(gè)源點(diǎn)一個(gè)匯點(diǎn)。
AOV網(wǎng)和AOE網(wǎng)都是用來(lái)工程建模的。但是它們還是有很大的不同。
- AOV網(wǎng)是頂點(diǎn)表示活動(dòng)的網(wǎng),它只是描述活動(dòng)之間的制約關(guān)系。
- AOE網(wǎng)是用邊表示活動(dòng)的網(wǎng),邊上的權(quán)值表示活動(dòng)持續(xù)的時(shí)間。
我們把路徑上各活動(dòng)持續(xù)的時(shí)間之和稱為路徑長(zhǎng)度,從源點(diǎn)到匯點(diǎn)具有最大長(zhǎng)度的路徑叫關(guān)鍵路徑。在關(guān)鍵路徑上的活動(dòng)稱為關(guān)鍵活動(dòng)。
相關(guān)參數(shù)
- 事件的最早發(fā)生時(shí)間etv(earliest time of vertex):即頂點(diǎn)Vk的最早發(fā)生時(shí)間。
- 事件的最晚發(fā)生時(shí)間ltv(lastest time of vertex):即頂點(diǎn)Vk的最晚發(fā)生時(shí)間,也就是每個(gè)頂點(diǎn)對(duì)應(yīng)的事件最晚需要開(kāi)始的時(shí)間,超出此時(shí)間,整個(gè)工程將會(huì)延誤整個(gè)工期。
- 活動(dòng)的最早開(kāi)工時(shí)間ete(earliest time of edge):即弧ak的最早發(fā)生時(shí)間。
- 活動(dòng)的最晚開(kāi)工時(shí)間lte(lastest time of edge):即弧ak的最晚發(fā)生時(shí)間,也就是不推遲工期的最晚開(kāi)工時(shí)間。
二、算法思路
- 求事件的最早發(fā)生時(shí)間etv的過(guò)程,就是我們從頭到尾找拓?fù)湫蛄械倪^(guò)程。
- 計(jì)算tlv其實(shí)就是把拓?fù)湫蛄械惯^(guò)來(lái)進(jìn)行。
三、代碼實(shí)現(xiàn)
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXEDGE 30
#define MAXVEX 30
#define INFINITYC 65535
typedef int Status; /* Status是函數(shù)的類型,其值是函數(shù)結(jié)果狀態(tài)代碼,如OK等 */
/* 鄰接矩陣結(jié)構(gòu) */
typedef struct
{
int vexs[MAXVEX];
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
}MGraph;
/* 鄰接表結(jié)構(gòu)****************** */
//邊表結(jié)點(diǎn)
typedef struct EdgeNode
{
//鄰接點(diǎn)域,存儲(chǔ)該頂點(diǎn)對(duì)應(yīng)的下標(biāo)
int adjvex;
//用于存儲(chǔ)權(quán)值,對(duì)于非網(wǎng)圖可以不需要
int weight;
//鏈域,指向下一個(gè)鄰接點(diǎn)
struct EdgeNode *next;
}EdgeNode;
//頂點(diǎn)表結(jié)點(diǎn)
typedef struct VertexNode
{
//頂點(diǎn)入度
int in;
//頂點(diǎn)域,存儲(chǔ)頂點(diǎn)信息
int data;
//邊表頭指針
EdgeNode *firstedge;
}VertexNode, AdjList[MAXVEX];
typedef struct
{
AdjList adjList;
//圖中當(dāng)前頂點(diǎn)數(shù)和邊數(shù)
int numVertexes,numEdges;
}graphAdjList,*GraphAdjList;
/* **************************** */
/* 關(guān)于AOE網(wǎng)圖的存儲(chǔ)代碼段-Begin */
//1.完成AOE網(wǎng)圖關(guān)于鄰接矩陣的存儲(chǔ)
void CreateMGraph(MGraph *G)/* 構(gòu)件圖 */
{
int i, j;
/* printf("請(qǐng)輸入邊數(shù)和頂點(diǎn)數(shù):"); */
G->numEdges=13;
G->numVertexes=10;
for (i = 0; i < G->numVertexes; i++)/* 初始化圖 */
{
G->vexs[i]=i;
}
for (i = 0; i < G->numVertexes; i++)/* 初始化圖 */
{
for ( j = 0; j < G->numVertexes; j++)
{
if (i==j)
G->arc[i][j]=0;
else
G->arc[i][j]=INFINITYC;
}
}
G->arc[0][1]=3;
G->arc[0][2]=4;
G->arc[1][3]=5;
G->arc[1][4]=6;
G->arc[2][3]=8;
G->arc[2][5]=7;
G->arc[3][4]=3;
G->arc[4][6]=9;
G->arc[4][7]=4;
G->arc[5][7]=6;
G->arc[6][9]=2;
G->arc[7][8]=5;
G->arc[8][9]=3;
}
//2.將鄰近矩陣轉(zhuǎn)化成鄰接表
void CreateALGraph(MGraph G,GraphAdjList *GL){
int i,j;
EdgeNode *e;
*GL = (GraphAdjList)malloc(sizeof(graphAdjList));
(*GL)->numVertexes=G.numVertexes;
(*GL)->numEdges=G.numEdges;
//讀入頂點(diǎn)信息,建立頂點(diǎn)表
for(i= 0;i <G.numVertexes;i++)
{
(*GL)->adjList[i].in=0;
(*GL)->adjList[i].data=G.vexs[i];
//將邊表置為空表
(*GL)->adjList[i].firstedge=NULL;
}
//建立邊表
for(i=0;i<G.numVertexes;i++)
{
for(j=0;j<G.numVertexes;j++)
{
if (G.arc[i][j]!=0 && G.arc[i][j]<INFINITYC)
{
e=(EdgeNode *)malloc(sizeof(EdgeNode));
//鄰接序號(hào)為j
e->adjvex=j;
e->weight=G.arc[i][j];
//將當(dāng)前頂點(diǎn)上的指向的結(jié)點(diǎn)指針賦值給e
e->next=(*GL)->adjList[i].firstedge;
//將當(dāng)前頂點(diǎn)的指針指向e
(*GL)->adjList[i].firstedge=e;
(*GL)->adjList[j].in++;
}
}
}
}
/* 關(guān)于AOE網(wǎng)圖的存儲(chǔ)代碼段-End! */
int *etv,*ltv; /* 事件最早發(fā)生時(shí)間和最遲發(fā)生時(shí)間數(shù)組,全局變量 */
int *stack2; /* 用于存儲(chǔ)拓?fù)湫蛄械臈?*/
int top2; /* 用于stack2的指針*/
//拓?fù)渑判?Status TopologicalSort(GraphAdjList GL){
//若GL無(wú)回路,則輸出拓?fù)渑判蛐蛄星曳祷貭顟B(tài)OK, 否則返回狀態(tài)ERROR;
EdgeNode *e;
int i,k,gettop;
//棧指針下標(biāo);
int top = 0;
//用于統(tǒng)計(jì)輸出的頂點(diǎn)個(gè)數(shù).作為拓?fù)渑判蚴欠翊嬖诨芈返呐袛嘁罁?jù);
int count = 0;
//建棧,將入度in = 0的頂點(diǎn)入棧;
int *stack = (int *)malloc(GL->numVertexes * sizeof(int));
//遍歷頂點(diǎn)表上入度in ?= 0 入棧
for (i = 0; i < GL->numVertexes;i++) {
//printf("%d %d\n",i,GL->adjList[i].in);
if ( 0 == GL->adjList[i].in ) {
stack[++top] = i;
}
}
//* stack2 的棧指針下標(biāo)
top2 = 0;
//* 初始化拓?fù)湫蛄袟? stack2 = (int *)malloc(sizeof(int) * GL->numVertexes);
//* 事件最早發(fā)生時(shí)間數(shù)組
etv = (int *)malloc(sizeof(GL->numVertexes * sizeof(int)));
//* 初始化etv 數(shù)組
for (i = 0 ; i < GL->numVertexes; i++) {
//初始化
etv[i] = 0;
}
printf("TopologicSort:\t");
while (top != 0) {
gettop = stack[top--];
printf("%d -> ", GL->adjList[gettop].data);
count++;
//將彈出的頂點(diǎn)序號(hào)壓入拓?fù)渑判虻臈V?
stack2[++top2] = gettop;
//例如gettop為V0 ,那么與V0相連接的結(jié)點(diǎn)就有etv[1] = 3; etv[2] = 4;
//例如gettop為V1 ,那么與V1連接的結(jié)點(diǎn)就有etv[4]= 3+6=9; etv[3] = 8;
//例如gettop為V2 ,那么與V2連接的結(jié)點(diǎn)就有etv[5]= 4+7=11; etv[3] = 12;
//例如gettop為V3 ,那么與V3連接的結(jié)點(diǎn)就有etv[4]= 12+3=15;
for(e = GL->adjList[gettop].firstedge; e; e = e->next)
{
k = e->adjvex;
//將i頂點(diǎn)連接的鄰接頂點(diǎn)入度減1,如果入度減一后為0,則入棧
if(!(--GL->adjList[k].in))
stack[++top] = k;
//求各頂點(diǎn)事件的最早發(fā)生的時(shí)間etv值
//printf("etv[gettop]+e->weight = %d\n",etv[gettop]+e->weight);
//printf("etv[%d] = %d\n",k,etv[k]);
if ((etv[gettop] + e->weight) > etv[k]) {
etv[k] = etv[gettop] + e->weight;
}
}
}
printf("\n");
//打印etv(事件最早發(fā)生時(shí)間數(shù)組)
// for (i = 0; i < GL->numVertexes; i++) {
// printf("etv[%d] = %d\n",i,etv[i]);
// }
// printf("\n");
if(count < GL->numVertexes)
return ERROR;
else
return OK;
return OK;
}
//求關(guān)鍵路徑, GL為有向網(wǎng),則輸出G的各項(xiàng)關(guān)鍵活動(dòng);
void CriticalPath(GraphAdjList GL){
EdgeNode *e;
int i,gettop,k,j;
//聲明活動(dòng)最早發(fā)生時(shí)間和最遲發(fā)生時(shí)間變量;
int ete,lte;
//求得拓?fù)湫蛄?計(jì)算etv數(shù)組以及stack2的值
TopologicalSort(GL);
//打印etv數(shù)組(事件最早發(fā)生時(shí)間)
printf("etv:\n");
for(i = 0; i < GL->numVertexes; i++)
printf("etv[%d] = %d \n",i,etv[i]);
printf("\n");
//事件最晚發(fā)生時(shí)間數(shù)組
ltv = (int *)malloc(sizeof(int) * GL->numVertexes);
//初始化ltv數(shù)組
for (i = 0; i < GL->numVertexes; i++) {
//初始化ltv數(shù)組. 賦值etv最后一個(gè)事件的值
ltv[i] = etv[GL->numVertexes-1];
//printf("ltv[%d] = %d\n",i,ltv[i]);
}
//計(jì)算ltv(事件最晚發(fā)生時(shí)間) 出棧求ltv
while (top2 != 0) {
//出棧(棧頂元素)
gettop = stack2[top2--];
//找到與棧頂元素連接的頂點(diǎn); 例如V0是與V1和V2連接
for (e = GL->adjList[gettop].firstedge; e; e = e->next) {
//獲取與gettop 相連接的頂點(diǎn)
k = e->adjvex;
//計(jì)算min(ltv[k]-e->weight,ltv[gettop])
if (ltv[k] - e->weight < ltv[gettop]) {
//更新ltv 數(shù)組
ltv[gettop] = ltv[k] - e->weight;
}
}
}
//打印ltv 數(shù)組
printf("ltv:\n");
for (i = 0 ; i < GL->numVertexes; i++) {
printf("ltv[%d] = %d \n",i,ltv[i]);
}
printf("\n");
//求解ete,lte 并且判斷l(xiāng)te與ete 是否相等.相等則是關(guān)鍵活動(dòng);
//2層循環(huán)(遍歷頂點(diǎn)表,邊表)
for(j=0; j<GL->numVertexes;j++)
{
for (e = GL->adjList[j].firstedge; e; e = e->next) {
//獲取與j連接的頂點(diǎn);
k = e->adjvex;
//ete 就是表示活動(dòng) <Vk, Vj> 的最早開(kāi)工時(shí)間, 是針對(duì)這條弧來(lái)說(shuō)的.而這條弧的弧尾頂點(diǎn)Vk 的事件發(fā)生了, 它才可以發(fā)生. 因此ete = etv[k];
ete = etv[j];
//lte 表示活動(dòng)<Vk, Vj> 的最晚開(kāi)工時(shí)間, 但此活動(dòng)再晚也不能等Vj 事件發(fā)生才開(kāi)始,而是必須在Vj 事件之前發(fā)生. 所以lte = ltv[j] - len<Vk, Vj>.
lte = ltv[k]-e->weight;
//如果ete == lte 則輸出j,k以及權(quán)值;
if (ete == lte) {
printf("<%d-%d> length:%d\n",GL->adjList[j].data, GL->adjList[k].data, e->weight);
}
}
}
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, 關(guān)鍵路徑的求解!\n");
MGraph G;
GraphAdjList GL;
CreateMGraph(&G);
CreateALGraph(G,&GL);
//拓?fù)渑判? //TopologicalSort(GL);
//關(guān)鍵路徑
CriticalPath(GL);
return 0;
}
四、算法復(fù)雜度
該算法使用了拓?fù)渑判?,拓?fù)渑判虻乃惴◤?fù)雜度為O(n+e),因此求解關(guān)鍵路徑的算法復(fù)雜度也為O(n+e)。