图的拓扑排序及关键路径C++实现(基于邻接表)

12 篇文章 0 订阅

AOV网:顶点表示活动,弧表示活动间的优先关系的有向图。无有向环。

AOE网:顶点表示事件,弧表示活动,权表示活动持续的时间的有向无环图。

1.拓扑排序(AOV网)

const int MAX_VERTEX_NUM = 20;
//图的邻接表存储:对图的每个顶点建立一个单链表。需要两种类型的节点。一种是表头节点(数量=图的顶点数),以向量形式存储,以便随机访问任一顶点的链表;
//一种是与表头节点表示的顶点邻接的顶点,链接到相应表头节点后面
//与表头节点表示的顶点邻接的顶点的节点,包括一个顶点的数据域adjvex和一个指向下一个与表头节点邻接的顶点的指针
typedef struct adjNode{
	int adjvex;
	struct adjNode *nextadj;
}adjNode;
//表头节点,包括顶点的数据域和指向第一个与其邻接的顶点的指针
typedef struct VNode {
	int data;
	int indegree = 0;  //入度
	adjNode *firstadj;
}VNode,adjList[MAX_VERTEX_NUM]; //数结构体组存储所有表头结点
//图
typedef struct {
	adjList vertices; //表头节点数组
	int vexnum, arcnum;  //图的顶点数和边数
}Graph;

//建立有向图
//时间复杂度:O(n+e)
void BuildAdjlist(Graph& G,int n,int e) {
	G.vexnum = n;
	G.arcnum = e;
	//先逐个画出图的全部顶点 从1到n编号
	for (int i = 1; i <= n; ++i) {
		G.vertices[i].data = i;
		G.vertices[i].firstadj = NULL;
	}
	for (int i = 0; i < e; ++i) {
		cout << "输入有向边:";
		int u, v;
		cin >> u >> v;
		//表头节点u的邻接表中应加一个顶点v,即插入一个顶点p(插到最前面)
		adjNode *p = new adjNode;
		p->adjvex = v;
		p->nextadj = G.vertices[u].firstadj;
		G.vertices[u].firstadj = p;
		G.vertices[v].indegree++;  //v顶点入度加1
		//delete p;	// 不可delete p!!正在构造链表
	}
}
//拓扑排序
bool toposort(Graph& G,vector<int>& res) {
	stack<int> s;
	for (int i = 1; i <= G.vexnum; ++i) {
		if (G.vertices[i].indegree == 0) {
			s.push(i);
		}
	}
	int count = 0;
	while (!s.empty()) {
		int j = s.top(); s.pop();
		count += 1;
		res.push_back(j);
		adjNode *p = G.vertices[j].firstadj;
		while (p != NULL) {
			int k = p->adjvex;
			G.vertices[k].indegree--;
			if (G.vertices[k].indegree == 0) {
				s.push(k);
			}
			p = p->nextadj;
		}
	}
	if (count < G.vexnum)  return false;  //图中存在有向环
	else return true;
}

//优化空间:用值为0的入度域存放链栈指针以指示下一个入度为0的顶点
bool toposort(Graph& G,vector<int>& res) {
	int top = 0; //指向入度为0的顶点
	for (int i = 1; i <= G.vexnum; ++i) {
		if (G.vertices[i].indegree == 0) {
			G.vertices[i].indegree = top;
			top = i;
		}
	}
	int count = 0;
	while (top!=0) {
		int j = top; 
		top = G.vertices[top].indegree; //相当于退栈
		count += 1;
		res.push_back(j);
		adjNode *p = G.vertices[j].firstadj;
		while (p != NULL) {
			int k = p->adjvex;
			G.vertices[k].indegree--;
			if (G.vertices[k].indegree == 0) {
				G.vertices[k].indegree = top;//新的入度为0的顶点入栈
				top = k;
			}
			p = p->nextadj;
		}
	}
	if (count < G.vexnum)  return false;  //图中存在有向环
	else return true;
}

int main() {
	Graph G;
	int n, e;
	cout<< "输入顶点数和边数:";
	cin >> n >> e;
	BuildAdjlist(G, n, e);
	//打印图的邻接表
	for (int i = 1; i <= n; ++i){
		cout << G.vertices[i].data << "(" << G.vertices[i].indegree << ")";
		if (G.vertices[i].firstadj == NULL) {
			cout << endl;
			continue;
		}
		adjNode* p = G.vertices[i].firstadj;
		while (p != NULL) {
			cout << "->" << p->adjvex;
			p = p->nextadj;
		}
		cout << endl;
	}
	vector<int> res;
	if (toposort(G, res) == true) {
		cout << "拓扑序列为:";
		for (auto& x : res) {
			cout << x << ends;
		}
	}
	else {
		cout << "图中存在有向环!" << endl;
	}
}

在这里插入图片描述

分析算法,对有n个顶点和e条弧的有向图而言,建立邻接表的时间复杂度为0(n+e);搜索入度为0的时间复杂度为O( n);在拓扑排序过程中,若有向图无环,则每个顶点进一次栈, 出一次栈,入度减1的操作在WHILE语句中总共执行e次,所以,总的时间复杂度为O(n + e)。上述拓扑排序的算法亦是下节讨论的求关键路径的基础。
当有向图中无环时,也可利用深度优先遍历进行拓扑排序,因为图中无环,则由图中某点出发进行深度优先搜索遍历时,最先退出DFS函数的顶点即出度为零的顶点,是拓扑有序序列中最后一个顶点。由此,按退出DFS函数的先后记录下来的顶点序列(如同求强连通分量时finished数组中的顶点序列)即为逆向的拓扑有序序列。
leetcode课程表

2.关键路径(AOE网)

关键路径: 任务计划作业图上的需要时间最长的路径(可有多条)。它决定完成总任务的时间。

工程的最小时间: 从开始顶点到结束顶点的最长路径的长(该路径上各活动的时间总和)。

用e(i)表示活动 ai 的最早开始时间;在不推迟整个工程完成的前提下,一个活动可以最晚开始的时间定义为该活动的最迟开始时间,用l(i)表示。

用e(i)- l(i)表示活动 ai 的时间余量,即 ai 在不影响总工期的前提下,可以延缓的时间。

e(i)=l(i)的表示活动 ai 称关键活动,显然关键路径上的所有活动都是关键活动,要加快工期只有提速关键活动的速度。

设活动 ai 用弧<j,k>表示,dut(<j,k>)表示权值。
e(i):活动 ai 的最早开始时间;
l(i):活动 ai 的最迟开始时间;
Ve(j):事件 j 的最早发生时间;
Vl(j):事件 j 的最迟发生时间;
则:e(i)=Ve(j) →演变为计算Ve
l(i)=Vl(k)-dut(<j , k>) →演变为计算求Vl

const int MAX_VERTEX_NUM = 20;
typedef struct adjNode{
	int adjvex;
	int dut;  //活动持续时间
	struct adjNode *nextadj;
}adjNode;
//表头节点,包括顶点的数据域和指向第一个与其邻接的顶点的指针
typedef struct VNode {
	int data;
	int indegree = 0;  //入度
	adjNode *firstadj;
}VNode,adjList[MAX_VERTEX_NUM]; //数结构体组存储所有表头结点
//图
typedef struct {
	adjList vertices; //表头节点数组
	int vexnum, arcnum;  //图的顶点数和边数
}Graph;

//建立有向图
void BuildAdjlist(Graph& G,int n,int e) {
	G.vexnum = n;
	G.arcnum = e;
	//先逐个画出图的全部顶点 从1到n编号
	for (int i = 1; i <= n; ++i) {
		G.vertices[i].data = i;
		G.vertices[i].firstadj = NULL;
	}
	for (int i = 0; i < e; ++i) {
		cout << "输入有向边及权:";  
		int u, v, w;
		cin >> u >> v >>w;
		//表头节点u的邻接表中应加一个顶点v,即插入一个顶点p(插到最前面)
		adjNode *p = new adjNode;
		p->adjvex = v;
		p->dut = w;
		p->nextadj = G.vertices[u].firstadj;
		G.vertices[u].firstadj = p;
		G.vertices[v].indegree++;  //v顶点入度加1
		//delete p;	// 不可delete p!!正在构造链表
	}
}

//在拓扑排序过程中计算各顶点的最早发生时间ve
//优化空间:用值为0的入度域indegree存放链栈指针以指示下一个入度为0的顶点.入度域初始放入度,运行过程可被拓扑栈和逆拓扑栈占用:拓扑出栈时,逆拓扑入栈
void critical_path(Graph& G) {
	vector<int> ve(G.vexnum+1), vl(G.vexnum+1); //事件(顶点)的最早发生时间
	int ftop = 0; //拓扑栈初始化
	int btop = 0; //逆拓扑栈初始化
	//求ve  在拓扑排序过程中计算各顶点的最早发生时间ve
	for (int i = 1; i <= G.vexnum; ++i) {
		if (G.vertices[i].indegree == 0) {
			G.vertices[i].indegree = ftop;
			ftop = i;
			ve[i] = 0;//初始化所有事件(顶点)的最早发生时间为0
		}
	}
	int count = 0;
	while (ftop!=0) {
		int j = ftop; 
		ftop = G.vertices[ftop].indegree; //相当于拓扑退栈
		G.vertices[j].indegree = btop;   btop = j; //逆拓扑入栈
		count += 1;

		adjNode *p = G.vertices[j].firstadj; //p是j的邻接点
		while (p != NULL) {
			int k = p->adjvex; 
			G.vertices[k].indegree--;
			if (G.vertices[k].indegree == 0) {
				G.vertices[k].indegree = ftop; //新的入度为0的顶点入栈
				ftop = k;
			}
			//计算顶点k的ve
			if (ve[k] < ve[j] + p->dut) {
				ve[k] = ve[j] + p->dut;
			}
			p = p->nextadj;
		}
	}
	if (count < G.vexnum) {
		cout << "图中存在有向环!" << endl;
		exit(0);
	}
	
	//求vl
	int endnode = btop; //逆拓扑序列第一个元素编号
	vl[endnode] = ve[endnode];  //初始化顶点事件最迟发生时间
	while (btop != 0) {
		int j = btop;
		btop = G.vertices[btop].indegree;
		vl[j] = ve[endnode];  //vl初始化均为最大值ve(n)
		adjNode* p = G.vertices[j].firstadj;
		while (p != NULL) {
			int k = p->adjvex;
			if (vl[j] > vl[k] - p->dut) {
				vl[j] = vl[k] - p->dut;
			}
			p = p->nextadj;
		}
	}
	//求e(i),l(i)和关键活动
	for (int i = 1; i <= G.vexnum; ++i) {
		adjNode* p = G.vertices[i].firstadj;
		char flag;
		while (p != NULL) {
			int ee = ve[i], el = vl[p->adjvex] - p->dut;
			if (ee == el) flag = '*';
			else flag = ' ';
			cout << "<" << i << "," << p->adjvex << ">" << "," << p->dut << "," << ee <<"="<< el << " " << flag << endl;
			p = p->nextadj;
		}
	}
}

int main() {
	Graph G;
	int n, e;
	cout<< "输入顶点数和边数:";
	cin >> n >> e;
	BuildAdjlist(G, n, e);
	//打印图的邻接表
	for (int i = 1; i <= n; ++i){
		cout << G.vertices[i].data << "(" << G.vertices[i].indegree << ")";
		if (G.vertices[i].firstadj == NULL) {
			cout << endl;
			continue;
		}
		adjNode* p = G.vertices[i].firstadj;
		while (p != NULL) {
			cout << "->" << p->adjvex;
			p = p->nextadj;
		}
		cout << endl;
	}

	critical_path(G);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值