目录
有向无环图
概念:无环的有向图(DAG)。有向无环图常用来描述一个工程或系统的进行过程。一个工程可以分为若干个子工程,只要完成了这些子工程,就可以导致整个工程的完成。
拓扑排序和关键路径属于有向无环图的应用。
拓扑排序
AOV网:
在现代化管理中,人们常用有向图来描述和分析一项工程的计划和实施过程,一个工程常被分为多个小的子工程,这些子工程被称为活动(Activity),在有向图中若以顶点表示活动,有向边表示活动之间的先后关系,这样的图简称为AOV网。
拓扑排序概念:对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。我们称这样的顶点序列为一个拓扑序列。拓扑排序就是对一个有向图构造拓扑序列的过程。
思路
(1) 选择一个入度为0的顶点并输出之。
(2) 从网中删除此顶点及所有出边。
(3)循环执行以上两步,直到不存在入度为0的顶点为止。
判断:循环结束后,若输出的顶点数小于网中的顶点数,则输出“有回路”信息,否则输出的顶点序列就是一种拓扑序列。
结构定义
#include<stdio.h>
#include<stdlib.h>
#define OK 1
#define ERROR 0
typedef char VertexType; //顶点类型
typedef int EdgeType; //边上权值
#define MAXVEX 100 // 最大顶点数
typedef struct EdgeNode { //边表结点
VertexType adjvex; //领接点域,存储对应下标
// EdgeType weight; //存储权值,如果是非网图可以省略
struct EdgeNode* next; //指向下一个邻接点
}EdgeNode;
typedef struct VertexNode { //顶点结点
int in; //顶点人度
VertexType data; //顶点域
EdgeNode* firstedge; //边表头指针
}VertexNode;
typedef struct VertexNode AdjList[MAXVEX]; //邻接表类型
typedef struct {
AdjList adjList;
int numNodes, numEdges; //图当前顶点数与边数
}GraphAdjList;
拓扑排序
/* 拓扑排序,如果 G有回路则输出 0,无回路输出 1 */
int TopologicalSort(GraphAdjList* G)
{
EdgeNode* e;
int i, k, gettop;
int top = 0; //栈指针下标
int count = 0; //统计输出顶点个数
int* stack; //建栈将入度为 0的顶点入栈
stack = (int*)malloc(G->numNodes * sizeof(int));
for (i = 0; i < G->numNodes; i++)
if (G->adjList[i].in == 0) //将入度为 0的顶点入栈
stack[++top] = i;
while (top != 0)
{
gettop = stack[top--]; //出栈
printf("%c -> ", G->adjList[gettop].data); //打印此顶点
count++;
e = G->adjList[gettop].firstedge;
while (e) //对此顶点弧表遍历
{
k = 0;
while (G->adjList[k].data != e->adjvex) //查找 e->adjvex在 G->adjList[t].data的位置
{
k++;
}
G->adjList[k].in--; //将 k号顶点邻接点的入度减 1
if (!G->adjList[k].in)
stack[++top] = k; //如果为 0则入栈
e = e->next;
}
}
if (count < G->numNodes)
return ERROR;
else
return OK;
}
拓扑排序全部代码(含有向邻接表)
我是用英文字母为顶点的,即 char VertexType; 顶点类型
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#define OK 1
#define ERROR 0
typedef char VertexType; //顶点类型
typedef int EdgeType; //边上权值
#define MAXVEX 100 // 最大顶点数
typedef struct EdgeNode { //边表结点
VertexType adjvex; //领接点域,存储对应下标
// EdgeType weight; //存储权值,如果是非网图可以省略
struct EdgeNode* next; //指向下一个邻接点
}EdgeNode;
typedef struct VertexNode { //顶点结点
int in; //顶点人度
VertexType data; //顶点域
EdgeNode* firstedge; //边表头指针
}VertexNode;
typedef struct VertexNode AdjList[MAXVEX]; //邻接表类型
typedef struct {
AdjList adjList;
int numNodes, numEdges; //图当前顶点数与边数
}GraphAdjList;
void CreateALGRAph(GraphAdjList*); //建立图的邻接表结构
int LocateVex(GraphAdjList, VertexType); //查找顶点
/* 拓扑排序,如果 G有回路则输出 0,无回路输出 1 */
int TopologicalSort(GraphAdjList*);
int main(void)
{
GraphAdjList G;
CreateALGRAph(&G);
// printf("!!!!!!!!!!_%d_!!!!!!!!\n",TopologicalSort(&G));
/* for (int i = 0; i < G.numNodes; i++) //打印邻接表
{
EdgeNode* e = (EdgeNode*)malloc(sizeof(EdgeNode));
e = G.adjList[i].firstedge;
printf("%c:", G.adjList[i].data);
while (e)
{
printf(" %c", e->adjvex);
e = e->next;
}
printf("\n");
}
*/
return 0;
}
/* 建立图的邻接表结构 */
void CreateALGRAph(GraphAdjList* G)
{
scanf("%d%d", &G->numNodes, &G->numEdges); //输入顶点数与边数
for (int i = 0; i < G->numNodes; i++) //建立顶点表
{
scanf(" %c", &G->adjList[i].data);
G->adjList[i].in = 0; //初始化
G->adjList[i].firstedge = NULL; //边表置为空
}
for (int k = 0; k < G->numEdges; k++) //建立边表
{
VertexType v1, v2;
int i, j; //用来返回下标
scanf(" %c %c", &v1, &v2); //输入一条边依附的两个顶点
i = LocateVex(*G, v1);
j = LocateVex(*G, v2);
EdgeNode* e1 = (EdgeNode*)malloc(sizeof(EdgeNode)); //生成边表结点(指针e1指向生成的内存空间)
e1->adjvex = G->adjList[j].data; //邻接序号
/* 头插法 */
e1->next = G->adjList[i].firstedge; //e1指向当前顶点指向结点
G->adjList[i].firstedge = e1; //当前顶点指向e1
G->adjList[j].in++; //顶点入度加 1
}
}
/* 查找顶点 */
int LocateVex(GraphAdjList G, VertexType v)
{
for (int i = 0; i < G.numNodes; i++)
if (v == G.adjList[i].data)
return i;
return -1;
}
/* 拓扑排序,如果 G有回路则输出 0,无回路输出 1 */
int TopologicalSort(GraphAdjList* G)
{
EdgeNode* e;
int i, k, gettop;
int top = 0; //栈指针下标
int count = 0; //统计输出顶点个数
int* stack; //建栈将入度为 0的顶点入栈
stack = (int*)malloc(G->numNodes * sizeof(int));
for (i = 0; i < G->numNodes; i++)
if (G->adjList[i].in == 0) //将入度为 0的顶点入栈
stack[++top] = i;
while (top != 0)
{
gettop = stack[top--]; //出栈
printf("%c -> ", G->adjList[gettop].data); //打印此顶点
count++;
e = G->adjList[gettop].firstedge;
while (e) //对此顶点弧表遍历
{
k = 0;
while (G->adjList[k].data != e->adjvex) //查找 e->adjvex在 G->adjList[t].data的位置
{
k++;
}
G->adjList[k].in--; //将 k号顶点邻接点的入度减 1
if (!G->adjList[k].in)
stack[++top] = k; //如果为 0则入栈
e = e->next;
}
}
if (count < G->numNodes)
return ERROR;
else
return OK;
}
关键路径
AOE网:在现代化管理中,人们常用有向图来描述和分析一项工程的计划和实施过程,一个工程常被分为多个小的子工程,这些子工程被称为活动(Activity),在带权有向图中若以顶点表示事件,有向边表示活动,边上的权值表示该活动持续的时间,这样的图简称为AOE网。
关键路径概念:我们把路径上各个活动所持续的时间之和称为路径长度,从源点(入度为0的顶点)到汇点(出度为0的顶点)具有最大路径长度的路径叫做关键路径。
关键活动:在关键路径上的活动叫关键活动。
大话数据结构这里讲的通俗易懂
思路
先定义 4个参数
• etv(vi)- 表示事件 vi最早发生时间
• ltv(vi)- 表示事件 vi最晚发生时间
• ete(ai)- 表示活动 ai最早发生时间
• lte(ai)- 表示活动 ai最晚发生时间
方法步骤:
-
求etv(i)、ltv(j).
从etv[0] = 0 开始向前递推 etv[k] = max{etv[i] + len<v~i~,v~k~>}, k ≠ 0 且 <v~i~,v~k~>∈P[k] P[k]表示所有到达顶点v~k~的弧的集合 从ltv[k] = etv[k] (k = n - 1) 开始向后递推 ltv[k] = min{ltv[j] - len<v~k~,v~j~>}, k < n - 1 且 <v~i~,v~k~>∈S[k] S[k]表示所有从顶点v~k~出发的弧的集合
-
求ete(i)、lte(j).
-
找 lte(i) == ete(i) 的关键活动.(包含第2个步骤)
(ete[i] 等于 lte[i] 即在关键路径上,上面的图讲的通俗易懂)设活动 ai用弧 <j, k> 表示,持续时间记为 len<v~j~, v~k~> , 则 ete[i] = etv[j] lte[i] = ltv[k] - len<v~j~, v~k~> 判断lte(i) 是否等于 ete(i)
结构定义
#include<stdio.h>
#include<stdlib.h>
#define OK 1
#define ERROR 0
typedef char VertexType; //顶点类型
typedef int EdgeType; //边上权值
#define MAXVEX 100 // 最大顶点数
/* 全局声明 */
int* etv, * ltv; //事件最早发生时间和最晚发生时间数组
int* stack2; //用于存储拓扑序列的栈
int top2; //用于stack2的指针
typedef struct EdgeNode { //边表结点
VertexType adjvex; //领接点域,存储对应下标
EdgeType weight; //存储权值,如果是非网图可以省略
struct EdgeNode* next; //指向下一个邻接点
}EdgeNode;
typedef struct VertexNode { //顶点结点
int in; //顶点人度
VertexType data; //顶点域
EdgeNode* firstedge; //边表头指针
}VertexNode;
typedef struct VertexNode AdjList[MAXVEX]; //邻接表类型
typedef struct {
AdjList adjList;
int numNodes, numEdges; //图当前顶点数与边数
}GraphAdjList;
改进的拓扑排序,便于求关键路径
/* 改进的拓扑排序,如果 G有回路则输出 0,无回路输出 1 */
int TopologicalSort(GraphAdjList* G)
{
EdgeNode* e;
int i, k, gettop;
int top = 0; //栈指针下标
int count = 0; //统计输出顶点个数
int* stack; //建栈将入度为 0的顶点入栈
stack = (int*)malloc(G->numNodes * sizeof(int));
for (i = 0; i < G->numNodes; i++)
if (G->adjList[i].in == 0) //将入度为 0的顶点入栈
stack[++top] = i;
//---------------------主要改动点------------------------
top2 = 0; //初始化
etv = (int*)malloc(G->numNodes * sizeof(int)); //事件最早发生时间数组
for (i = 0; i < G->numNodes; i++)
etv[i] = 0; //初始化
stack2 = (int*)malloc(G->numNodes * sizeof(int)); //初始化拓扑序列栈
//-------------------------------------------------------
while (top != 0)
{
gettop = stack[top--]; //出栈
count++;
//--------------------------------
stack2[++top2] = gettop; //将弹出的顶点序号压入拓扑序列的栈
//--------------------------------
e = G->adjList[gettop].firstedge;
while (e) //对此顶点弧表遍历
{
k = 0;
while (G->adjList[k].data != e->adjvex) //查找 e->adjvex在 G->adjList[t].data的位置
{
k++;
}
G->adjList[k].in--; //将 k号顶点邻接点的入度减 1
if (!G->adjList[k].in)
stack[++top] = k; //如果为 0则入栈
//----------------------------------
if ((etv[gettop] + e->weight) > etv[k])
etv[k] = etv[gettop] + e->weight;
//----------------------------------
e = e->next;
}
}
if (count < G->numNodes)
return ERROR;
else
return OK;
}
求关键路径,G为有向网,输出 G的各项关键活动
/* 求关键路径,G为有向网,输出 G的各项关键活动*/
void CriticalPath(GraphAdjList* G)
{
EdgeNode* e;
int i, gettop, k, j;
int ete, lte; //活动最早发生时间与最迟发生时间变量
TopologicalSort(G); //求拓扑序列,计算数组 etv和 stack2的值
ltv = (int*)malloc(G->numNodes * sizeof(int)); //事件最晚发生时间数组
for (i = 0; i < G->numNodes; i++)
ltv[i] = etv[G->numNodes - 1]; //初始化ltv
while (top2 != 0)
{
gettop = stack2[top2--];
e = G->adjList[gettop].firstedge;
while (e)
{
k = 0;
while (G->adjList[k].data != e->adjvex) //查找 e->adjvex在 G->adjList[t].data的位置
{
k++;
}
if (ltv[k] - e->weight < ltv[gettop]) //求各顶点事件最晚发生时间ltv
ltv[gettop] = ltv[k] - e->weight;
e = e->next;
}
}
/* 求 ete, ltv和关键活动 */
for (j = 0; j < G->numNodes; j++)
{
e = G->adjList[j].firstedge;
while (e)
{
k = 0;
while (G->adjList[k].data != e->adjvex) //查找 e->adjvex在 G->adjList[t].data的位置
{
k++;
}
ete = etv[j]; //活动最早发生时间
lte = ltv[k] - e->weight; //活动最晚发生时间
if (ete == lte) //如果相等即在关键路径上
printf("<%c - %c> length: %d",
G->adjList[j].data, G->adjList[k].data, e->weight);
e = e->next;
}
}
}
关键路径全部代码(含有向权值邻接表)
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#define OK 1
#define ERROR 0
typedef char VertexType; //顶点类型
typedef int EdgeType; //边上权值
#define MAXVEX 100 // 最大顶点数
int* etv, * ltv; //事件最早发生时间和最晚发生时间数组
int* stack2; //用于存储拓扑序列的栈
int top2; //用于stack2的指针
typedef struct EdgeNode { //边表结点
VertexType adjvex; //领接点域,存储对应下标
EdgeType weight; //存储权值,如果是非网图可以省略
struct EdgeNode* next; //指向下一个邻接点
}EdgeNode;
typedef struct VertexNode { //顶点结点
int in; //顶点人度
VertexType data; //顶点域
EdgeNode* firstedge; //边表头指针
}VertexNode;
typedef struct VertexNode AdjList[MAXVEX]; //邻接表类型
typedef struct {
AdjList adjList;
int numNodes, numEdges; //图当前顶点数与边数
}GraphAdjList;
void CreateALGRAph(GraphAdjList*); //建立图的邻接表结构
int LocateVex(GraphAdjList, VertexType); //查找顶点
/* 拓扑排序,如果 G有回路则输出 0,无回路输出 1 */
int TopologicalSort(GraphAdjList*);
/* 求关键路径,G为有向网,输出 G的各项关键活动*/
void CriticalPath(GraphAdjList*);
int main(void)
{
GraphAdjList G;
CreateALGRAph(&G);
CriticalPath(&G);
/*for (int i = 0; i < G.numNodes; i++) //打印邻接表
{
EdgeNode* e = (EdgeNode*)malloc(sizeof(EdgeNode));
e = G.adjList[i].firstedge;
printf("%c:", G.adjList[i].data);
while (e)
{
printf(" %c", e->adjvex);
e = e->next;
}
printf("\n");
}
*/
return 0;
}
/* 建立图的邻接表结构 */
void CreateALGRAph(GraphAdjList* G)
{
scanf("%d%d", &G->numNodes, &G->numEdges); //输入顶点数与边数
for (int i = 0; i < G->numNodes; i++) //建立顶点表
{
scanf(" %c", &G->adjList[i].data);
G->adjList[i].in = 0; //初始化
G->adjList[i].firstedge = NULL; //边表置为空
}
for (int k = 0; k < G->numEdges; k++) //建立边表
{
VertexType v1, v2;
int i, j; //用来返回下标
scanf(" %c %c", &v1, &v2); //输入一条边依附的两个顶点
i = LocateVex(*G, v1);
j = LocateVex(*G, v2);
EdgeNode* e1 = (EdgeNode*)malloc(sizeof(EdgeNode)); //生成边表结点(指针e1指向生成的内存空间)
scanf("%d", &e1->weight); //输入 v1与 v2的边的权值
e1->adjvex = G->adjList[j].data; //邻接序号
/* 头插法 */
e1->next = G->adjList[i].firstedge; //e1指向当前顶点指向结点
G->adjList[i].firstedge = e1; //当前顶点指向e1
G->adjList[j].in++; //顶点入度加 1
}
}
/* 查找顶点 */
int LocateVex(GraphAdjList G, VertexType v)
{
for (int i = 0; i < G.numNodes; i++)
if (v == G.adjList[i].data)
return i;
return -1;
}
/* 改进的拓扑排序,如果 G有回路则输出 0,无回路输出 1 */
int TopologicalSort(GraphAdjList* G)
{
EdgeNode* e;
int i, k, gettop;
int top = 0; //栈指针下标
int count = 0; //统计输出顶点个数
int* stack; //建栈将入度为 0的顶点入栈
stack = (int*)malloc(G->numNodes * sizeof(int));
for (i = 0; i < G->numNodes; i++)
if (G->adjList[i].in == 0) //将入度为 0的顶点入栈
stack[++top] = i;
//----------------------------------------------主要改动点
top2 = 0; //初始化
etv = (int*)malloc(G->numNodes * sizeof(int)); //事件最早发生时间数组
for (i = 0; i < G->numNodes; i++)
etv[i] = 0; //初始化
stack2 = (int*)malloc(G->numNodes * sizeof(int)); //初始化拓扑序列栈
//-------------------------------------------------------
while (top != 0)
{
gettop = stack[top--]; //出栈
count++;
//--------------------------------
stack2[++top2] = gettop; //将弹出的顶点序号压入拓扑序列的栈
//--------------------------------
e = G->adjList[gettop].firstedge;
while (e) //对此顶点弧表遍历
{
k = 0;
while (G->adjList[k].data != e->adjvex) //查找 e->adjvex在 G->adjList[t].data的位置
{
k++;
}
G->adjList[k].in--; //将 k号顶点邻接点的入度减 1
if (!G->adjList[k].in)
stack[++top] = k; //如果为 0则入栈
//----------------------------------
if ((etv[gettop] + e->weight) > etv[k])
etv[k] = etv[gettop] + e->weight;
//----------------------------------
e = e->next;
}
}
if (count < G->numNodes)
return ERROR;
else
return OK;
}
/* 求关键路径,G为有向网,输出 G的各项关键活动*/
void CriticalPath(GraphAdjList* G)
{
EdgeNode* e;
int i, gettop, k, j;
int ete, lte; //活动最早发生时间与最迟发生时间变量
TopologicalSort(G); //求拓扑序列,计算数组 etv和 stack2的值
ltv = (int*)malloc(G->numNodes * sizeof(int)); //事件最晚发生时间数组
for (i = 0; i < G->numNodes; i++)
ltv[i] = etv[G->numNodes - 1]; //初始化ltv
while (top2 != 0)
{
gettop = stack2[top2--];
e = G->adjList[gettop].firstedge;
while (e)
{
k = 0;
while (G->adjList[k].data != e->adjvex) //查找 e->adjvex在 G->adjList[t].data的位置
{
k++;
}
if (ltv[k] - e->weight < ltv[gettop]) //求各顶点事件最晚发生时间ltv
ltv[gettop] = ltv[k] - e->weight;
e = e->next;
}
}
/* 求 ete, ltv和关键活动 */
for (j = 0; j < G->numNodes; j++)
{
e = G->adjList[j].firstedge;
while (e)
{
k = 0;
while (G->adjList[k].data != e->adjvex) //查找 e->adjvex在 G->adjList[t].data的位置
{
k++;
}
ete = etv[j]; //活动最早发生时间
lte = ltv[k] - e->weight; //活动最晚发生时间
if (ete == lte) //如果相等即在关键路径上
printf("<%c - %c> length: %d",
G->adjList[j].data, G->adjList[k].data, e->weight);
e = e->next;
}
}
}