拓扑排序与关键路径(有向无环图的应用)

     以下内容主要参考与严蔚敏版的数据结构。
     用顶点表示活动,用弧表示活动之间的优先关系的有向图称为顶点表示活动的网(Activity On Vertex Network),简称AOV网。这种网可以用来表示一个施工流程图、一个产品的生产流程或者是某个专业的必修课程前后顺序关系。在AOV网中由一个顶点A指向另一个顶点B的箭头表示活动A必须在活动B开始之前完成,活动B也只有在活动A完成之后才能开始。在AOV网中不应该出现环,因为环意味着某项活动以自己为先决条件,显然这是荒谬的。如果设计出这样的工程图,工程便无法进行。拓扑排序算法就是用来设计出的工程流程是否可行。一个流程图的例子如图1所示。该流程图由9个活动组成,活动 V 2 V_2 V2 V 3 V_3 V3 V 4 V_4 V4必须在活动 V 1 V_1 V1完成后才能开始,但是活动 V 2 V_2 V2 V 3 V_3 V3 V 4 V_4 V4可以并行的执行。

 
图1.

     拓扑排序算法的流程为:在图中找到入度为0的节点并取出,然后将以刚才被取出的节点为尾的弧的头节点的入度减一并删除刚才被取出的节点以及以刚才被取出的节点为尾的弧。迭代执行以上操作直到图中所有节点已经取出或者图中不存在入度为0的节点。如果取出的节点数为图中节点的总数则该项目流程是可行的,否则不可行。针对图1的拓扑排序流程如图2、3、4、5、6所示。

 
图2.
 
图3.
 
图4.
 
图5.
 
图6.

     用顶点表示事件,边表示活动,用弧表示事件之间的优先关系的有向图称为边表示活动的网(Activity On Edge Network),简AOE网。AOE网中边的权值(即活动的权值)表示活动持续的时间等。通常AOE网可以用来估算工程的完成时间。AOE网的一个简单例子如图7所示。

 
图7.

     在图7中一共有 v 1 − > v 9 v_1->v_9 v1>v9 9个事件, a 1 − > a 11 a_1->a_{11} a1>a11 11个活动。每个事件表示的状态是在它之前的活动已经完成,在它之后的活动可以开始。如事件 v 1 v_1 v1表示整个工程的开始,事件 v 9 v_9 v9表示整个工程的结束。事件 v 5 v_5 v5表示活动 a 4 a_4 a4 a 5 a_5 a5已经完成,活动 a 7 a_7 a7 a 8 a_8 a8可以开始。活动 a 1 a_1 a1需要花费6天时间,活动 a 2 a_2 a2需要花费4天时间,活动 a 3 a_3 a3需要花费5天时间。
     与AOV网不同的是AOE网的节点表示事件(也即一种状态),因此一般AOE网一般只有一个入度为零的节点(源点:工程的开始)以及只有一个出度为零的节点(汇点:工程的结束)(在没有环的情况下),可以将图7的AOE网改造成图10的AOV网。AOV网研究的问题是整个工程的流程是否可行(没有环),AOE网研究的问题是:

  • 完成整项工程至少需要多长时间?
  • 那些活动是影响工程进度的关键?

     由于在AOE网中有些活动可以并行的进行,因此完成整项工程的最少时间应该为图中从源点到汇点的所有路径中权值和最大的路径,这样的路径叫做关键路径。从图7中可以看出活动 a 1 a_1 a1 a 4 a_4 a4完成一共需要7天时间,活动 a 2 a_2 a2 a 5 a_5 a5完成一共需要5天时间。如果要进行活动 a 7 a_7 a7 a 8 a_8 a8必须要等到活动 a 1 a_1 a1 a 4 a_4 a4 a 2 a_2 a2 a 5 a_5 a5都完成后才能进行,虽然活动 a 2 a_2 a2 a 5 a_5 a5完成一共只需要5天时间,但是得等到 a 1 a_1 a1 a 4 a_4 a4完成后活动 a 7 a_7 a7 a 8 a_8 a8才能开始。
     假设 v l i vl_{i} vli为事件 v i v_i vi的最晚发生时间。假设 v e i ve_{i} vei为事件 v i v_i vi的最早发生时间,它是从源点到事件 v i v_i vi节点的所有路径中权值和最大的路径。这个时间决定了所有以事件 v i v_i vi为弧尾的活动的最早开始时间。我们用 e i e_i ei表示活动 a i a_i ai的最早开始时间, l i l_i li表示活动 a i a_i ai的最晚开始时间(这是在不推迟整个工程完成时间的前提下),两者之差 l i l_i li- e i e_i ei意味着完成活动 a i a_i ai的时间余量。我们把 l i l_i li= e i e_i ei的活动叫做关键活动,关键路径上的活动都是关键活动,提前完成非关键活动并不能加快整个工程的进度。因此分析关键路径的目的是辨别哪些是关键活动,以便争取提高关键活动放入功效,缩短整个工期。
     求关键路径就是求 l i l_i li= e i e_i ei的活动,要求活动 a i a_i ai的最早开始时间 e i e_i ei以及最晚开始时间 l i l_i li。可以先求得图中各个事件的最早开始时间 v e i ve_{i} vei以及最晚开始时间 v l i vl_{i} vli。事件的最早开始时间 v e i ve_{i} vei以及最晚开始时间 v l i vl_{i} vli算法如下:

  • v e 0 = 0 ve_{0}=0 ve0=0(事件0表示源点),其它事件的最早开始时间可以通过式子 v e i = M a x { v e j + w e i g h t < j , i > } ve_{i}=Max\{ve_{j}+weight<j,i>\} vei=Max{vej+weight<j,i>}按照从源点到汇点的拓扑有序序列推出。 < j , i > <j,i> <j,i>是所有以事件 v i v_{i} vi为弧头的弧, w e i g h t < j , i > weight<j,i> weight<j,i>是弧的权重。(其模型如图8所示)
  • v l n − 1 = v e n − 1 vl_{n-1}=ve_{n-1} vln1=ven1(事件 n − 1 n-1 n1表示汇点),其它事件的最晚开始时间可以通过式子 v l i = M i n { v l j − w e i g h t < i , j > } vl_{i}=Min\{vl_{j}-weight<i,j>\} vli=Min{vljweight<i,j>}按照从源点到汇点的拓扑有序序列的逆序列推出。 < i , j > <i,j> <i,j>是所有以事件 v i v_{i} vi为弧尾的弧, w e i g h t < i , j > weight<i,j> weight<i,j>是弧的权重。(其模型如图9所示)
 
图8.
 
图9.
 
图10.

     如果弧 < j , k > <j,k> <j,k>表示活动 a i a_i ai,则活动 a i a_i ai的最早开始时间 e i e_i ei以及最晚开始时间 l i l_i li分别为: e i = v e j e_i=ve_{j} ei=vej以及最晚开始时间 l i = v l k − w e i g h t < j , k > l_i=vl_{k}-weight<j,k> li=vlkweight<j,k>。然后找到最早开始时间和最晚开始时间相同的活动即为关键活动。

//图的邻接表表示
//弧节点
class GraphArcNode
{
private:
	int weight;
	int adjVertexIndex;
	GraphArcNode* nextArcNode;
public:
	GraphArcNode(int d = 0, int index = 0)
	{
		weight=d;
		adjVertexIndex=index;
		nextArcNode = nullptr;
	}
	void setNextArcNode(GraphArcNode* next)
	{
		nextArcNode = next;
	}
	int getWeight()
	{
		return weight;
	}
	int getAdjVertexIndex()
	{
		return adjVertexIndex;
	}
	GraphArcNode* getNextArcNode()
	{
		return nextArcNode;
	}
};
//图节点
class GraphNode
{
private:
	int data;
	GraphArcNode* nextArcNode;
public:
	GraphNode(int d = 0)
	{
		data = d;
		nextArcNode = nullptr;
	}
	void setNextArcNode(GraphArcNode* next)
	{
		nextArcNode = next;
	}
	int getData()
	{
		return data;
	}
	GraphArcNode* getNextArcNode()
	{
		return nextArcNode;
	}
};
//图的数据结构
class ALGraph
{
private:
	vector<GraphNode> graphNodeSet;
	int nodeNum;
public:
	ALGraph(int num)
	{
		nodeNum = num;
	}
	void addGraphNode(int data)
	{
		graphNodeSet.push_back(GraphNode(data));
	}
	int getGraphNodeNum()
	{
		return nodeNum;
	}
	vector<GraphNode> getGraphNodeSet()
	{
		return graphNodeSet;
	}
	void setGraphNodeArc(int graphNodeIndex, int weight, int adjVertexIndex)
	{
	    if (graphNodeSet[graphNodeIndex].getNextArcNode() == nullptr)
	    {
	    	graphNodeSet[graphNodeIndex].setNextArcNode(new GraphArcNode(weight, adjVertexIndex));
	    }
	    else
	    {
	    	GraphArcNode* currentArcNode = graphNodeSet[graphNodeIndex].getNextArcNode();
	    	GraphArcNode* nextArcNode = graphNodeSet[graphNodeIndex].getNextArcNode()->getNextArcNode();
	    	while (nextArcNode != nullptr)
	    	{
	    		currentArcNode = nextArcNode;
	    		nextArcNode = nextArcNode->getNextArcNode();
	    	}
	    	currentArcNode->setNextArcNode(new GraphArcNode(weight, adjVertexIndex));
	    }	
	}
};

//求图中各个节点的入度
void computeGraphNodeInDgree(ALGraph g, vector<int>& inDrgee)
{
	vector<GraphNode> graphNodeSet = g.getGraphNodeSet();
	for (int i = 0; i < g.getGraphNodeNum(); i++)
	{
		int currentInDegree = 0;
		for (int j = 0; j < g.getGraphNodeNum(); j++)
		{
			if (j != i)
			{
				GraphArcNode* currentArcNode = graphNodeSet[j].getNextArcNode();
				while (currentArcNode != nullptr)
				{
					if (i == currentArcNode->getAdjVertexIndex())
					{
						currentInDegree = currentInDegree + 1;
					}
					currentArcNode = currentArcNode->getNextArcNode();
				}
			}
		}
		inDrgee[i] = currentInDegree;
	}
}

//拓扑排序算法
int TopologicalOrder(ALGraph g, stack<int> & inverseTopoSquence, vector<int> &ve)
{
	vector<int> currentInDrgee(g.getGraphNodeNum(),0);
	stack<int> zeroInDegreeNode;
	computeGraphNodeInDgree(g, currentInDrgee);
	for (int i = 0; i < g.getGraphNodeNum(); i++)
	{
		if (currentInDrgee[i] == 0)
			zeroInDegreeNode.push(i);
	}
	int count = 0;
	while (!zeroInDegreeNode.empty())
	{
		int currentZeroInDegreeNodeIndex = zeroInDegreeNode.top();
		cout << "Node " << g.getGraphNodeSet()[currentZeroInDegreeNodeIndex].getData() << endl;
		zeroInDegreeNode.pop();
		inverseTopoSquence.push(currentZeroInDegreeNodeIndex);
		count++;
		for (GraphArcNode* p = g.getGraphNodeSet()[currentZeroInDegreeNodeIndex].getNextArcNode(); p != nullptr; p = p->getNextArcNode())
		{
			int k = p->getAdjVertexIndex();
			currentInDrgee[k] = currentInDrgee[k] - 1;
			if (currentInDrgee[k] == 0)
			{
				zeroInDegreeNode.push(k);
			}

			if ((ve[currentZeroInDegreeNodeIndex] + p->getWeight()) > ve[k])
				ve[k] = ve[currentZeroInDegreeNodeIndex] + p->getWeight();
		}
	}
	if (count < g.getGraphNodeNum())
		return 0;
	else
		return 1;
}

//关键路径算法
int criticalPath(ALGraph g)
{
	stack<int> inverseTopoSquence;
	vector<int> ve(g.getGraphNodeNum(), 0);
	if (!TopologicalOrder(g, inverseTopoSquence, ve))
		return 0;
		
	vector<int> vl=ve;
	while (!inverseTopoSquence.empty())
	{
		int i = inverseTopoSquence.top();
		//由于vl初始化为ve,所以除开汇点的对于vl中的每一个元素必须在第一次被赋值之后才能进行比较操作;
		bool firstArc = true;
		cout << "i=" << i << endl;
		inverseTopoSquence.pop();
		int k = 0;
		int duration = 0;
		for (GraphArcNode* p = g.getGraphNodeSet()[i].getNextArcNode(); p != nullptr; p = p->getNextArcNode())
		{
			k = p->getAdjVertexIndex();
			cout << "k=" << k << endl;
			duration = p->getWeight();
			if (firstArc)
			{
				vl[i] = vl[k] - duration;
				firstArc = false;
			}
            //由于vl初始化为ve,所以除开汇点的对于vl中的每一个元素必须在第一次被赋值之后才能进行比较操作;
			if (((vl[k] - duration) < vl[i])&& (firstArc==false))
			{
				cout << "true" << endl;
				vl[i] = vl[k] - duration;
			}
		}
	}
    //在事件的最早开始时间和最晚开始时间的基础上求活动的最早开始时间以及最晚开始时间和关键路径
	for (int j=0;j<g.getGraphNodeNum();j++)
	{
		int k = 0;
		int duration = 0;
		int ee = 0;
		int el = 0;
		bool tag = false;
		for (GraphArcNode* p = g.getGraphNodeSet()[j].getNextArcNode(); p != nullptr; p = p->getNextArcNode())
		{
			k = p->getAdjVertexIndex();
			duration = p->getWeight();
			ee = ve[j];
			el = vl[k]-duration;
			tag = (ee == el) ? true : false;
			if (tag)
			{
				cout << "Activity: " << j << "->" << k << " , duration=" << duration << endl;
			}
		}
	}
	return 1;
}
//针对图7的测试程序
int main()
{
	ALGraph g(9);
	for (int i = 1; i < 10; i++)
	{
		g.addGraphNode(i);
	}
	g.setGraphNodeArc(0, 6, 1);
	g.setGraphNodeArc(0, 4, 2);
	g.setGraphNodeArc(0, 5, 3);
	g.setGraphNodeArc(1, 1, 4);
	g.setGraphNodeArc(2, 1, 4);
	g.setGraphNodeArc(3, 2, 5);
	g.setGraphNodeArc(4, 9, 6);
	g.setGraphNodeArc(4, 7, 7);
	g.setGraphNodeArc(5, 4, 7);
	g.setGraphNodeArc(6, 2, 8);
	g.setGraphNodeArc(7, 4, 8);
	int status= criticalPath(g);
	cout << "status=" << status << endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qqssss121dfd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值