用拓扑排序-求关键路径
首先,我们来了解一下,什么是拓扑排序。百度百科这样说:对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
简单来说,由某个集合上的一个派内需得到该集合上的一个全序,这个操作称为拓扑排序。那么偏序又是什莫尼?定义为:若集合X上的关系R是自反的,反对称的和传递的,则称R是集合X上的偏序关系。那全序呢?设R是集合X上的偏序,如果对于每一个x,y属于X必由xRy或yRx,则称R是集合X上的全序关系。
那关键路径是什么呢?关键路径是指设计中从输入到输出经过的延时最长的逻辑路径。优化关键路径是一种提高设计工作速度的有效方法。一般地,从输入到输出的延时取决于信号所经过的延时最大路径,而与其他延时小的路径无关。在优化设计过程中关键路径法可以反复使用,直到不可能减少关键路径延时为止。EDA工具中综合器及设计分析器通常都提供关键路径的信息以便设计者改进设计,提高速度。
下面是一个求关键路径的代码(可以参考):
(数据结构-严蔚敏 图7.29)
因为里面要排序,所以会需要用到栈,头文件里面把栈的各种先提前定义好,方便在主文件中调用。
#include <stdlib.h>
#include<string.h>
typedef char SElemType; //栈数据元素的类型
#define STACK_INIT_SIZE 10 //栈存储空间的初始分配量
#define STACKINCREMENT 10 //栈存储空间的分配增量
typedef struct
{
SElemType *base; //构造之前和销毁之后,base的值为NULL
SElemType *top; //栈顶指针
int stacksize; //当前分配的存储容量(以sizeof(SElemType)为单位)
}Stack;
int InitStack(Stack &S)
{
S.base=(SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType)); //通过malloc函数分配空间
if (!S.base)
return 0; //如果分配失败,则返回0
S.top=S.base; //栈顶指针设为栈底地址
S.stacksize=STACK_INIT_SIZE; //当前分配的空间
return 1;
}
int StackEmpty(Stack S)
{
if (S.base)
{
if (S.base==S.top) //如果栈为空栈,则返回1(true),否则返回0(false)
{
return 1;
}
}
return 0;
}
int Push(Stack &S,int e)
{
if (S.base)
{
if ((S.top-S.base)>=S.stacksize) //栈满,追加存储空间
{
S.base=(SElemType *)realloc(S.base,(S.stacksize+STACKINCREMENT)* sizeof(SElemType));
if(S.base==NULL)
return 0; //如果重新分配空间失败,则返回0(false),表示插入不成功
S.top=S.base+S.stacksize; //设置数组的新地址
S.stacksize+=STACKINCREMENT; //分配空间要加上新增的空间
}
*S.top++=e;
return 1;
}
return 0;
}
//栈顶位置后++,本次运算S.top栈空间不变,所以是把S栈顶指针指向E,运算完成后栈空间才自增1
//*++S.top=e
//栈顶位置前++,本次运算S.top栈顶向前增加1,所以是把S栈新增的栈顶指向E。
int Pop(Stack &S,int &e)
{
if (S.top==S.base) //若栈不是空的
{
return 0;
}
e=*--S.top;
return 1;
}
下面是主程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zhan.h"
#define GF G->adjlist[i].firstarc
#define INFINITY 32767 // 值∞这里是当作int型为2字节
static int in[9]={
0},ou[9]={
0};
static int ve[9]={
0};
static int vl[9]={
0};
const int MAX_VERTEX_NUM=9; // 最大顶点个数
// 图的数组(邻接矩阵)存储表示//
typedef struct {
//VertexType vexs[MAX_VERTEX_NUM]; // 顶点向量
int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵数组
int n,e; // 图的当前顶点数和弧数
} MGraph;
void CreateMat(MGraph &g, int A[][MAX_VERTEX_NUM], int n, int e) //创建图的邻接矩阵
{
g.n=n,g.e=e;
for(int i=0; i<MAX_VERTEX_NUM; i++)
for(int j=0; j<MAX_VERTEX_NUM; j++) {
g.arcs[i][j]=A[i][j];
}
}
// 图的邻接表存储表示//
typedef struct ArcNode {
// 链表结点
int adjvex; // 该弧所指向的顶点的位置
int weight; // 网的权值
ArcNode *nextarc; // 指向下一条弧的指针
} ArcNode;
typedef struct VNode{
ArcNode data; // 顶点信息
ArcNode *firstarc; // 第一个表结点的地址,指向第一条依附该顶点的弧的指针
}VNode,AdjList[MAX_VERTEX_NUM];
typedef struct {
AdjList vertices;
VNode adjlist[MAX_VERTEX_NUM];
int n, e; // 图的当前顶点数和弧数
} ALGraph;
int init(ALGraph *&G)
{
G=(ALGraph*)malloc(sizeof(ALGraph));
for(int i=0; i<MAX_VERTEX_NUM; i++) {
GF=(ArcNode*)malloc(sizeof(ArcNode));
if(!GF) return 0;//创建失败
GF->nextarc=NULL;
}
}
int DestroyAdj (ALGraph *&G) //销毁图的邻接表
{
//printf("销毁\n");
ArcNode*p;
for(int i=0; i<MAX_VERTEX_NUM; i++) {
p=GF;
while(GF) {
p=GF->nextarc;
free(GF);
GF=p;
}
}
free(G);
return 1;
}
void CreateAdj (ALGraph *&G, int A[][MAX_VERTEX_NUM], int n, int e) //创建图的邻接表
{
int in=0;
init(G);
G->n=n;
G->e=e;
for(int i=0; i<n; i++){
for(int j=n-1,in=0; j>=0; j--) {
if(A[i][j]!=0) {
ArcNode* p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=j;
p->weight=A[i][j];
p->nextarc=GF->nextarc;
GF->nextarc=p;
in++;
}
}
ou[i]=in;
// printf("节点%d出度:%d\n",i,in);
}
}
void CreateAdjn (ALGraph *&G, int A[][MAX_VERTEX_NUM], int n, int e) //创建图的逆邻接表
{
init(G);
G->n=n;
G->e=e;
for(int i=0; i<n; i++){
for(int j=n-1,in1=0; j>=0; j--) {
if(A[i][j]!=0) {
ArcNode* p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=j;
p->weight=A[i][j];
p->nextarc=GF->nextarc;
GF->nextarc=p;
in1++;
}
}
in[i]=in1;
//printf("节点%d入度:%d\n",i,in1);
}
}
void DispAdj_List(ALGraph *G) //输出邻接表G
{
ArcNode *p;
printf("图 G 的邻接表: \n");
for(int i=0; i<MAX_VERTEX_NUM; i++) {
printf(" %d:->",i+1);
p=GF->nextarc;
if(GF->nextarc==NULL)continue;
for(int j=0; j<MAX_VERTEX_NUM; j++) {
if(p->adjvex==j) {
printf("(%d,%d)",p->adjvex+1,p->weight);
if(p->nextarc!=NULL) {
printf("->");
p=p->nextarc;
} else
printf("\n");
}
}
}
printf("\n");
}
//邻接表邻接矩阵的相互转化//
void MatrixToList(MGraph g, ALGraph *&G) //将邻接矩阵g 转换成邻接表G
{
//printf("将g转换为邻接表 G \n");
init(G);
CreateAdj (G,g.arcs,9,10);
}
void MatrixToListn(MGraph g, ALGraph *&G) //将邻接矩阵g 转换成逆邻接表G
{
//printf("将g转换为逆邻接表 G \n");
init(G);
CreateAdjn (G,g.arcs,9,10);
}
void topological(ALGraph *G) //拓扑排序函数
{
int i,j, k,count=0;
int e;
ArcNode *p ;
Stack S,T;
InitStack(S);
InitStack(T);
printf("拓扑排序:");
for(i = 0; i <G->n; i++){
if(!in[i]) Push(S,i);
while(!StackEmpty(S)){
Pop(S,i);
Push(T,i);
printf("%d\t",i+1);
count++;
for(p=GF->nextarc;p;p=p->nextarc){
k=p->adjvex;
if(!(--in[k])) Push(S,k);
if(ve[i]+p->weight>ve[k])
ve[k]=ve[i]+p->weight;
}
}
}
printf("\n");
printf("逆拓扑排序:");
for(j = 0; j <G->n||(!StackEmpty(S)); j++){
Pop(T,e);
printf("%d\t",e+1);
}
printf("\n");
if(count<G->n) printf("存在回路\n");
else printf("不存在回路\n");
for(int x=0;x<9;x++){
printf("节点%d的最早发生时间为:%d\n",x+1,ve[x]);
}
}
void topologicaln(ALGraph *G) //逆拓扑排序函数
{
int i, k;
vl[8]=ve[8];
ArcNode *p ;
Stack S;
InitStack(S);
for(i = G->n-1; i >=0; i--){
if(!ou[i]) Push(S,i);
while(!StackEmpty(S)){
Pop(S,i);
for(p=GF->nextarc;p;p=p->nextarc){
k=p->adjvex;
if(!(--ou[k])) Push(S,k);
vl[k]=vl[i]-p->weight;
if(vl[i]-p->weight<vl[k])
vl[k]=vl[i]-p->weight;
}
}
}
printf("\n");
for(int x=0;x<9;x++){
printf("节点%d的最晚发生时间为:%d\n",x+1,vl[x]);
}
}
void Critical(ALGraph *G){
ArcNode *p ;
int i,k,dut,ee,el,lo=0,x=0,y=0;
printf("关键路径:\n");
for(i=0;i<G->n;i++){
for(p=GF->nextarc;p;p=p->nextarc){
k=p->adjvex;
dut=p->weight;
ee=ve[i];
el=vl[k]-dut;
if(ee==el){
if((i==x&&i!=0)||k==y)continue;
printf("%d点->%d点 路径长度为%d ee:%d el;%d\n",i+1,k+1,dut,ee,el);
lo=lo+dut;
x=i;
y=k;}
else continue;
}
}
printf("长度为:%d\n",lo);
}
void main()
{
MGraph g,g1;//邻接矩阵
ALGraph *G;//邻接表
int n[9][9]={
0};
int m[9][9]= {
{
0,6,4,5,0,0,0,0,0},
{
0,0,0,0,1,0,0,0,0},
{
0,0,0,0,1,0,0,0,0},
{
0,0,0,0,0,2,0,0,0},
{
0,0,0,0,0,0,9,7,0},
{
0,0,0,0,0,0,0,4,0},
{
0,0,0,0,0,0,0,0,2},
{
0,0,0,0,0,0,0,0,4},
{
0,0,0,0,0,0,0,0,0}
};
//1.创建并输出邻接矩阵g
CreateMat(g,m,9,10);
//DispMatrix(g);
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
n[i][j]=m[j][i];
}
}
CreateMat(g1,n,9,10);
//2.邻接矩阵g转换成邻接表G
ALGraph *G1;//为测试转化另设一个邻接表G1
ALGraph *G2;
MatrixToList(g,G1);
DispAdj_List(G1);
MatrixToListn(g1,G2);
topological(G1);
//DispAdj_List(G2);
topologicaln(G2);
Critical(G1);
DestroyAdj(G1);//5.销毁邻接表G,G1
DestroyAdj(G2);
}
运行结果如图:
由上面的代码,可以看见,这里我没有选择采用外部输入的方式,这里我们主要关心关键路径的求解过程,我这里采用二维数组的存储方式,进行图的存储,好处就是存的时候,比较好操作,也为逆拓扑排序的时候,提供了一条简单的方法,我们可以将二维数组转置,然后直接调用拓扑排序就可以,思维量也会下来,但是代码运行速度也会下来,因为转置的时候,需要开辟一个和原来数组相同大小的空间,要是图很大,还是推荐使用标准的逆拓扑排序。