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);
}