[C language] critical path/longest path

1. Problem description

1. Topological sorting:

In order to better complete the project in the AOV network, the sequence of activities must be satisfied, and each activity needs to be arranged in a sequence that is topological sorting. Topological sorting can be applied to the arrangement of teaching plans, and according to the dependence between courses, the arrangement of teaching courses can be made. According to the number of courses input by the user, the number of prioritized relationships between courses, and the prioritized relationship between courses, the program should provide a topologically sorted course arrangement plan after the program is executed. For example, the course priority relationship shown in the figure below:
Insert picture description here
Insert picture description here

The result of topological sorting should be given after the program is executed:
(C1, C2, C3, C4, C5, C7, C9, C10, C11, C6, C12, C8)
or (C9, C10, C11, C6, C1, C12 , C4, C2, C3, C5, C7, C8), or other sequences that meet the requirements.

2. Critical path:

Usually, the plan, construction process, production process, program flow, etc. are all regarded as a project. A project is usually divided into several sub-projects called "activities". After completing these "activities", the project can be completed. AOE-Net is usually used to represent engineering. AOE-net is a weighted directed acyclic graph, in which the vertex represents the event (EVENT), the arc represents the activity, and the weight represents the duration of the activity.
AOE-net can be used to estimate the completion time of the project. Can make people understand:
(1) How much time is required to study a certain project at least?
(2) Which activities are the key to affecting the progress of the project?
Since some activities in the AOE-net can be carried out in parallel, there may be more than one directed path from the starting point to each vertex, so that there may be more than one directed path from the starting point to the completion point, and the length of these paths may also be different. Although the time required to complete the activities of different paths is different, the project is considered complete only if all activities on each path are completed. Therefore, the shortest time required to complete the project is the length of the longest path from the starting point to the completion point, that is, the sum of the duration of all activities on this path. This path length is called the critical path (Critical Path) .
Example: The AOE diagram is as follows:
Insert picture description here

After the program is executed, it should output: the
key activities are a1, a4, a7, a10, a8, a11 and the
key path is: a1->a4->a7->a10 (or V1->V2->V5->V7->V9 ) And a1->a4->a8->a11 (or V1->V2->V5->V8->V9)
takes at least 18 (time units).

Two, design ideas

Main data structure : linked list, sequential stack, sequential queue.
Main algorithm design : adjacency table stores the relationship between vertices, a two-dimensional array stores arc weights (activity duration), stack-assisted topological sorting calculates vertex ve, vl and then arcs E and l. Using a two-dimensional array that records the key activities that have been visited, combined with the recursive DFS, realizes the output of multiple complete critical paths.

Three, specific source code and comments

#include <stdio.h>
#include <stdlib.h>

int arr[9][9]; //存放weight的二维数组
int mark[99] = {
    
     0 };//记录下表对应顶点是否为关键顶点

int label[9][9] = {
    
     0 };//用于记录两个顶点之间是否存在关键路径
int totalT = 0;//记录关键路径总时长,即工程完成至少时间
int cnt = 0;//用于记录某一条关键路径的顶点数。

//声明结构体:结点 
struct Node {
    
    
	int index; //编号 
	int inDegree; //入度 
	int ve; //最早的发生时间,默认为0 
	int vl; //最晚的发生时间,默认为无穷大 
	struct Node* next; //下一个结点的指针 
};

int stack[100]; //用栈辅助实现拓扑排序,用数组实现栈
int top = 0; //栈的头元素的index 
int stack2[9] = {
    
     -1 };//用于用于记录路径上关键顶点的顺序(倒序)
int top2 = 0;

int queue[100]; //一个用数组实现的队列 
int front = 0; //队列的头 
int rear = 0; //队列的尾 

//存储方式:邻接表
//用一个数组存放首元素。每个首元素后面用指针连接各个结点 
struct Node arrNode[9];

//邻接表存储各个顶点(之间的关系)
//头插法,得到的结果的次序是反的
void addNode(int parentIndex, int nodeIndex)
{
    
    
	struct Node* parentNode = &arrNode[parentIndex];
	struct Node* temp = (struct Node*)(malloc(sizeof(struct Node)));

	temp->index = nodeIndex;
	temp->next = parentNode->next;
	parentNode->next = temp;

	arrNode[nodeIndex].inDegree++; //入度 + 1
	temp->inDegree = arrNode[nodeIndex].inDegree;
}

//遍历一个结点以及它的所有相连的结点 
void traverse(int size)
{
    
    
	for (int i = 0; i < size; i++)
	{
    
    
		struct Node* node = &arrNode[i];
		while (node != NULL)
		{
    
    
			printf("%d --> ", node->index);
			node->ve = 0;
			node->vl = 32676;//顺便初始化每个顶点的ve和vl
			node = node->next;
		}
		printf("∧\r\n");
	}
}

int max(int a, int b) //求两个数中的最大值 
{
    
    
	return a > b ? a : b; //三元运算符
}

int min(int a, int b) //求两个数中的最小值
{
    
    
	return a < b ? a : b; //三元运算符
}

//用二维数组储存图的weight,此处为权重值的初始化
void initMap(int size)
{
    
    
	for (int i = 0; i < size; i++)
	{
    
    
		for (int j = 0; j < size; j++)
		{
    
    
			arr[i][j] = 0;
		}
	}
}

//栈辅助实现的拓扑排序 
int TopologicalOrderByStack(int size) 
{
    
    
	int count = 0; //用来计数
	int index = 0; //输出的结点的编号 
	int i = 0; //循环变量 

	//1、遍历所有结点,寻找入度为0的结点,并把编号存放在stack中 
	for (i = 0; i < size; i++)
	{
    
    

		if (arrNode[i].inDegree == 0)
		{
    
    
			stack[top] = i; //把结点的编号存放到stack中 
			top++; //top + 1 
		}
	}

	//2、弹出栈中的结点,输出结点编号。同时让该结点的下一级结点的入度-1 
	//3、循环,直到栈中的结点为0,即top == 0 
	while (top > 0)
	{
    
    
		top--; //top的位置没有内容,所以要先 - 1 
		index = stack[top]; //得到存放在stack中的编号 
		queue[rear] = index; //把编号存放到queue中 
		rear++; //存放数据后,队列的rear + 1 
		count++; //计数 + 1 

		//从arrNode中获得结点的指针 
		struct Node* parentNode = &arrNode[index]; //得到数组arrNode中的指定节点 
		struct Node* node = parentNode->next; //得到arrNode中的指定节点的子节点

		//遍历
		while (node != NULL) //如果子节点不是NULL就循环 
		{
    
    
			int sonIndex = node->index; //子节点的index 

			//计算子节点的ve
			//遍历arrNode数组可以是确保可以考虑到每条弧的,因此可以通过更新的方式确保某个结点的ve是最大值
			arrNode[sonIndex].ve = max(arrNode[sonIndex].ve, arrNode[index].ve + arr[index][sonIndex]);
			
			//从arrNode中获得结点、结点信息 
			if (arrNode[sonIndex].inDegree > 0) //子节点的入度 > 0 
			{
    
    
				//结点的inDegree - 1 
				arrNode[sonIndex].inDegree--;

				//如果结点的inDegree == 0
				if (arrNode[sonIndex].inDegree == 0)
				{
    
    
					//把结点的index (也就是sonIndex) 加入到stack中 
					stack[top] = sonIndex;
					top++;
				}
			}
			node = node->next; //指针指向下一个子节点 
		}
	}

	//判断是否存在环
	if (count < size) //如果输出的结点数 < 总结点数 
	{
    
    
		return 0; //存在环,即不存在拓扑排序
	}
	return 1; //存在拓扑排序 
}


void showCriticalPathByDFS(int parentNode)
{
    
    
	if (parentNode == queue[rear - 1])//递归结束的条件是:遇到结束项目,表示已经生成一条完整的关键路径
	{
    
    
		for (int i = 0;i<=cnt ; i++)
		{
    
    
			printf("V%d", stack2[i]);
			if (i != cnt)printf("-->");
		}
		printf("\n");//删除最后多余的-->并换行
	}
	else for (struct Node* p = arrNode[parentNode].next; p; p = p->next)
	{
    
    
		if (label[parentNode][p->index] == 1)
		{
    
    
			label[parentNode][p->index] = 0;
			stack2[++top2] = p->index;
			cnt++;
			showCriticalPathByDFS(p->index);
			label[parentNode][p->index] = 1;//还原
			top2--; cnt--;
		}
	}
}


void getKeyRoute(int size) //关键路径 
{
    
    
	//最终结点的最晚发生时间就是它的最早发生时间
	arrNode[queue[rear - 1]].vl = arrNode[queue[rear - 1]].ve;

	//从终点往起点开始,反着计算
	for (int i = rear - 1; i >= front; i--)
	{
    
    
		for (int j = i; j >= front; j--)
		{
    
    
			if (arr[queue[j]][queue[i]] > 0)
			{
    
    
				//注意,这是一个有向图。所以不能用arr[queue[i]][queue[j]],
				//arr[queue[i]][queue[j]]的值一定是0,用arr[queue[j]][queue[i]] 
				arrNode[queue[j]].vl = min(arrNode[queue[j]].vl, arrNode[queue[i]].vl - arr[queue[j]][queue[i]]);
			}
		}
	}

	//记录关键顶点(下标对应)
	for (int i = 0; i < size; i++)
	{
    
    
		if (arrNode[i].ve == arrNode[i].vl)
		{
    
    
			mark[i] = 1;
		}
	}

	printf("各个顶点的Ve和Vl:\n");
	for (int i = 0; i < size; i++)
	{
    
    
		printf("V%d : ",arrNode[i]);printf("ve = %d   |   vl = %d\n", arrNode[i].ve, arrNode[i].vl);
	}
	printf("\r\n\r\n各个活动及其e和l:\r\n");
	//通过邻接表的下标的顺序遍历,查找关键路径
	for (int i = 0; i < size; i++)
	{
    
    
		struct Node* p = arrNode[i].next;
		while (p)
		{
    
    
			printf("(v%d-->v%d)\t e = %d   |   l = %d", i, p->index, arrNode[i].ve, arrNode[p->index].vl - arr[i][p->index]);
			if (arrNode[i].ve == arrNode[p->index].vl - arr[i][p->index])    //活动的e = l即为关键活动
			{
    
    
				label[i][p->index] = 1;
				printf("  此活动为关键活动");
			}
			printf("\n");
			
			p = p->next;
		}
	}
	printf("\r\n\r\n关键路径:\r\n");
	stack2[0] = arrNode[queue[front]].index;//先入栈存储拓扑排序第一个顶点的下标
	//因为下面的操作是从第一个顶点的关键邻接顶点开始DFS的
	showCriticalPathByDFS(queue[front]);
}

int main(void)
{
    
    
	int vexnum, arcnum;
	printf("请输入顶点数:\n");
	scanf("%d", &vexnum);
	printf("请输入弧数:\n");
	scanf("%d", &arcnum);
	initMap(vexnum); //初始化图 (weight)
	//结点信息初始化 
	for (int i = 0; i < vexnum; i++)
	{
    
    
		arrNode[i].index = i;
		arrNode[i].inDegree = 0;
		arrNode[i].next = NULL;
	}
	
	printf("请连接顶点并赋值权重(起点,终点,权重):\n");
	for (int i = 0; i < arcnum; i++)
	{
    
    
		int b, e, w;
		scanf("%d,%d,%d", &b, &e, &w);
		addNode(b, e);
		arr[b][e] = w;
	}

	printf("\n邻接表输出:\n");
	//对每个节点遍历它的相邻节点
	traverse(vexnum);

	if (TopologicalOrderByStack(vexnum))
	{
    
    
		printf("\n存在拓扑排序\r\n");
	}
	else
	{
    
    
		printf("\n不存在拓扑排序\r\n");
		exit(0);
	}
	//完成拓扑排序和计算ve 
	getKeyRoute(vexnum); //计算关键路径 
	int startNode = queue[front];
	printf("该工程至少完成时间为:%d", arrNode[queue[rear-1]].vl);//即为拓扑排序最后一个顶点的ve或vl

	return 0;
}



Guess you like

Origin blog.csdn.net/qq_40463117/article/details/112472674