關鍵路徑JS實現

PAT-python-zjw發表於2019-01-09

我勒個去,畢業論文終於提交了,趕緊完成圖基本演算法的最後一節。。。

1 定義

在一個表示工程的帶權有向圖中,用頂點表示事件,用有向邊表示活動,用邊上的權值表示活動的持續時間,這種有向圖的邊表示活動的網,稱為AOE網。AOE網中沒有入邊的頂點稱為始點或源點,沒有出邊的頂點稱為終點或匯點。由於一個工程通常總有一個開始和一個結束,因此AOE網只有一個源點和匯點。

與AOV網區別在於,AOV網是頂點表示活動的網,它只描述活動之間的制約關係,而AOE網是用邊表示活動的網,邊上的權值表示活動持續的時間。其是建立在活動之間制約關係沒有矛盾的基礎之上,再來分析整個工程需要多少時間。
在這裡插入圖片描述

我們把路徑上各個活動所持續的時間之和稱為路徑長度,從源點到匯點具有最大長度的路徑叫關鍵路徑,在關鍵路徑上的活動叫關鍵活動。只有找出AOE網中的關鍵路徑,並縮短路徑上關鍵活動的工作時間,才能最有效的縮短完成工程的時間。

2 建立測試網

在這裡插入圖片描述
按照拓撲排序的鄰接表結構,建立測試網:

class vex{
	constructor(value){
		this.data = value;
		this.firstEdge = null;
		this.in = 0;   //用於存放頂點的入度
	}

}

class adjvex{
	constructor(node,weight){
		this.node = node;
		this.weight = weight;
		this.next = null;
	}
}

class Graph{
	constructor(v,vr){
		let len = v.length;
		let vexs = new Array(len);
		let v1=0,v2=0;
		let newvex = null;
		for (let i=0;i<len;i++){
			vexs[i] = new vex(v[i]);
		}
		for (let arc of vr){
			v1 = v.indexOf(arc[0]);
			v2 = v.indexOf(arc[1]);

			newvex = new adjvex(v2,arc[2]);
			newvex.next = vexs[v1].firstEdge;   //頭插法
			vexs[v1].firstEdge = newvex;
			vexs[v2].in++;
		}
		this.adjList = vexs;
	}
}

let a = new Graph(['v0','v1','v2','v3','v4','v5','v6','v7','v8','v9','v10','v11','v12','v13'],[['v0','v11',1],['v0','v4',1],['v0','v5',1],['v1','v4',1],['v1','v8',1],['v1','v2',1],['v2','v5',1],['v2','v6',1],['v3','v2',1],['v3','v13',1],['v4','v7',1],['v5','v8',1],['v5','v12',1],['v6','v5',1],['v8','v7',1],['v9','v11',1],['v9','v10',1],['v10','v13',1],['v12','v9',1]]);
console.log(a);

在這裡插入圖片描述
在這裡插入圖片描述

3 演算法思路

在AOE網中找到關鍵路徑在於如何找到關鍵活動。對此我們定義幾個引數:

  • 事件的最早發生時間etv:即頂點代表的事件最早發生的時間
  • 事件的最晚發生時間ltv:即頂點代表的事件最晚發生的時間
  • 活動最早的開工時間ete:即弧代表的活動最早發生時間
  • 活動最晚的開工時間lte:即弧代表的活動最晚發生時間,也就是不推遲工期的最晚開工時間。

例如下圖,有4個事件: v0,v1,v2和v3,以及4個活動:a0,a1,a2和a4。事件v3必須等到活動a2和a4完成以後才能觸發,因此在不推遲工期的條件下,事件v3的最早發生時間和最晚發生時間都是12,且v2的最早和最晚發生時間也都是4,但是對於事件v1,其只要保證在時間7之前發生即可,因此其最早發生時間是3,而最晚發生時間是7。綜上所述,要減少整個工程的時間,對於縮減活動a0和a2的時間並沒有什麼作用,關鍵要縮減經過事件頂點v2的活動a1和a4的時間。因此經過頂點v2的路徑才是關鍵路徑。由此我們可以看出,一個活動是否為關鍵活動在於活動最早的開工時間ete是否等於活動最晚的開工時間lte,若等於,該活動則為關鍵活動,否則則不是。
在這裡插入圖片描述
在關鍵路徑演算法,我們先求得頂點事件的最早和最晚發生時間,再求得弧活動的最早和最晚開工時間,最後比較弧活動的兩者時間是否相等,判斷是否為關鍵活動。

4 程式碼

整個求解關鍵路徑過程可劃分為三部分:

  • 求解各頂點事件的最早發生時間
  • 求解各頂點事件的最晚發生時間
  • 求解各弧上活動的最早和最晚發生時間,並做關鍵活動的判斷

首先求得各頂點事件的最早發生時間,可以看到程式碼與拓撲排序很相似。求各頂點的最早發生時間判定條件如下
在這裡插入圖片描述
例如下圖,etv[1] + a2 < etv[2] + a4,所以etv[3] = 12。
在這裡插入圖片描述

function getEtvs(G){
	let etvs = [];   //用於儲存各頂點的最早發生時間
	let stack = [], T = [];   //stack為輔助棧,T為儲存拓撲序列的陣列
	let count = 0;    //用於統計頂點個數

	for (let i=0;i<G.adjList.length;i++){   //初始化陣列,並將入度為0的頂點推入棧
		etvs[i] = 0;
		if (G.adjList[i].in === 0){
			stack.push(i);
		}
	}

	let currentAdjVex = null;
	let currentIndex = 0;
	while(stack.length > 0){
		currentIndex = stack.pop();    //彈出棧頂入度為0的頂點
		count++;
		T.push(currentIndex);
		currentAdjVex = G.adjList[currentIndex].firstEdge;
		while(currentAdjVex){        //遍歷當前頂點的所有鄰接頂點
			if (etvs[currentIndex] + currentAdjVex.weight > etvs[currentAdjVex.node]){   //關鍵程式碼,求各頂點事件最早發生時間
				etvs[currentAdjVex.node] = etvs[currentIndex] + currentAdjVex.weight;
			}

			if (--G.adjList[currentAdjVex.node].in === 0){   //將當前鄰接頂點入度減1,若等於0,則推入棧中
				stack.push(currentAdjVex.node);
			}

			currentAdjVex = currentAdjVex.next;
		}
	}

	if (count < G.adjList.length){
		return false;
	}else{
		return [etvs,T];
	}
}

接著根據各頂點事件的最早發生時間,反向求各頂點的最晚發生時間,相當於把拓撲排序倒過來,判定條件如下
在這裡插入圖片描述
例如下圖,頂點v6的最晚發生時間為25,而頂點v7為19,ltv[6] - 9 > ltv[7] -4,為了保證工程不延期,因此頂點v4最晚發生時間ltv[4] = 15
在這裡插入圖片描述

function getLtvs(G,etvs,T){
	let ltvs = [];

	for (let i=0;i<G.adjList.length;i++){   //初始化每個頂點的最晚發生時間
		ltvs[i] = etvs[G.adjList.length-1];
	}

	let currentAdjVex = null;
	let currentIndex = 0;
	while(T.length > 0){
		currentIndex = T.pop();   //反向拓撲序列計算
		currentAdjVex = G.adjList[currentIndex].firstEdge;
		while(currentAdjVex){
			if (ltvs[currentIndex] > ltvs[currentAdjVex.node] - currentAdjVex.weight){  //關鍵程式碼,求各頂點事件的最晚發生時間
				ltvs[currentIndex] = ltvs[currentAdjVex.node] - currentAdjVex.weight;
			}

			currentAdjVex = currentAdjVex.next;
		}
	}

	return ltvs;
}

最後即可根據各頂點事件的最早和最晚發生時間,計算弧上活動的最早和最晚時間,並作比較判斷是否為關鍵活動。注意的是,弧上活動<vi,vj>的最早開工時間不可能早於頂點vi事件最早發生時間,而最晚開工時間則不可能晚於頂點vj事件最晚發生時間 - 弧上活動持續時間。因此程式碼如下:

function criticalPath(G){
	let etvs = null, ltvs = null;
	let T = null;

	[etvs,T] = getEtvs(G);  //呼叫函式獲取圖各頂點事件的最早和最晚發生時間
	ltvs = getLtvs(G, etvs, T);

	let ete = 0, lte = 0;
	let currentAdjVex = null;
	for (let i=0;i<G.adjList.length;i++){   //遍歷每一條邊
		currentAdjVex = G.adjList[i].firstEdge;
		while(currentAdjVex){
			ete = etvs[i];     //弧上活動的最早開工時間
			lte = ltvs[currentAdjVex.node] - currentAdjVex.weight;   //弧上活動的最晚開工時間
			if (ete === lte){
				console.log('v'+i,'v'+currentAdjVex.node,currentAdjVex.weight);
			}
			currentAdjVex = currentAdjVex.next;
		}
	}
}

在這裡插入圖片描述

相關文章