图的拓扑排序和关键路径——数据结构实习

这一章看了好长时间,花了一个下午才搞懂,其中关键路径还是挺有意思的。一开始我是想先拓扑排序,然后按顺序求关键路径,但是用贪心是明显不行的,后来想用动态规划,但是憋了十几分钟,没憋出来,最后参考了一下网上其他人的写法,总结出来这篇。

#pragma once	//杂注,使头文件只编译一次 

#include<iostream> 
#include<stdio.h>
#include<string.h>
#include<cstdlib>
#include<cmath>
#include<stack>
#include<algorithm>
#define INF 0xfffff
#define maxn 10000
using namespace std;

int Ve[maxn];	//存储事件的最早发生时间 
int Vl[maxn];	//存储事件的最迟发生时间 
int indegree[10000];	//建立一个入度数组,入度为零,证明就可以进栈了 
//用邻接表建立有向无圈图,先进行拓扑排序,然后求关键路径;

//邻接表的结构体 
typedef struct ArcNode {	//弧结构体 
	int adjvex;       //邻接点域,存放与Vi邻接的点在表头数组中的位置
	struct ArcNode *nextarc; 	//链域,指示下一条边或弧
	//InfoType  *info;     //存储与边或弧相关的信息,如权值
	int info;
} ArcNode;

typedef struct VNode{	//顶点结构体 
	char data; //存放顶点信息
	ArcNode *firstarc; //指示链表中第一个结点
	//int indegree;  //表示一共有多少个点指向这个节点 
} VNode;

typedef struct{
	VNode vex[maxn];   // 顶点表列(邻接表)
	int vex_num, arc_num; // 图的当前顶点数和弧数
} Graph;

int LocateVex(Graph G, char v) {
	for(int i=0;i<G.vex_num;i++) {
		if(v == G.vex[i].data)
			return i;
	}
    return -1;  
}
void CreateGraph(Graph &G) {
	ArcNode *p, *s; 
	char v1, v2;
	int power;	//各条边的权值
	cout<<"请输入节点数和边数目"<<endl;
	cin>>G.vex_num>>G.arc_num;
	
	getchar();  //因为定义的data是 char 类型的,所以这里必须要吃掉一个回车   
    
	cout<<"请输入节点"<<endl; 
    for(int i=0;i<G.vex_num;i++) {
    	cin>>G.vex[i].data;
    	G.vex[i].firstarc = NULL;
	}
	
	cout<<"输入各条边和他们的权值:"<<endl;
	for(int i=0;i<G.arc_num;i++) {
		getchar();			//char 类型的,每次都要吃掉一个回车 
		scanf("%c %c %d", &v1, &v2, &power);
		int j = LocateVex(G, v1);
		int k = LocateVex(G, v2);
		s = (ArcNode *) malloc (sizeof(ArcNode));
		s->adjvex = k;
		s->info = power;
		s->nextarc = NULL;
		if(!G.vex[j].firstarc)	//将v2 直接接到列的顶点的上面; 
			G.vex[j].firstarc = s;
		else {
			p = G.vex[j].firstarc;	//如果这个数据不是第一个接到列顶点上面的,那么就顺次向后移 
			while(p->nextarc)	//直到移到最后 
				p = p->nextarc;
			p->nextarc = s;
		}
	}  
}

void FindInputDgeree(Graph G){	//计算所有节点的入度  
	ArcNode *p;
    for(int i=0;i<G.vex_num;i++)
    	indegree[i] = 0;
    for(int i=0;i<G.vex_num;i++) {
    	p = G.vex[i].firstarc;
    	while(p) {
    		indegree[p->adjvex] ++;
    		p = p->nextarc;
		}
	}
}


void Topology(Graph G) {
	stack<int> s;
	FindInputDgeree(G);  
    int count=0, i;  
	ArcNode *p;
	
	cout<<"拓扑排序序列如下:"<<endl;
	
	for(i=0;i<G.vex_num;i++)
		if(!indegree[i])
			s.push(i);	//把入度为零的节点入栈 
    
    while(!s.empty()){  
        i = s.top();	//获得栈顶 
		s.pop();		//弹出栈; 
        cout<<G.vex[i].data<<endl;
        count++;
        p = G.vex[i].firstarc;  
        while(p) {         
            if(! (--indegree[p->adjvex]) )//判断去掉一条边后节点的入度是否为零    
        		s.push(p->adjvex);
            p = p->nextarc;
        }         
    }
    
    if(count < G.vex_num)
        cout<<"这是一个有圈的图"<<endl;
	cout<<endl;  
}


void CriticalPath(Graph G) {  
	int i, k, e, l;   
	ArcNode * p;  
 	//以下是求时间最早发生时间 
	for(i = 0; i < G.vex_num; i++)  
    	Ve[i] = 0;  //最早发生时间全部初始化为0 	
	for(i = 0; i < G.vex_num; i++) {  
        ArcNode *p = G.vex[i].firstarc;  
        while(p != NULL) {  
            k = p->adjvex;  
            if(Ve[i] + p->info > Ve[k])		//动态求得当前最长,也就是最早时间  
            	Ve[k] = Ve[i] + p->info;  
            p = p->nextarc;
        }
   }
   //以下是求最迟发生时间    
	for(i = 0; i < G.vex_num; i++)  
		Vl[i] = Ve[G.vex_num-1];	//对最迟发生时间进行初始化  
	for(i = G.vex_num-2; i >= 0; i--) { //后推  
        	p = G.vex[i].firstarc;  
        	while(p != NULL) {  
            	k = p->adjvex;  
                if(Vl[k] - p->info < Vl[i])  
                    Vl[i] = Vl[k] - p->info;  
                p = p->nextarc;  
          	}
   }
  
   for(i = 0; i < G.vex_num; i++) {  
        p = G.vex[i].firstarc;  
		while(p != NULL) {  
            k = p->adjvex;  
            e = Ve[i];              //最早开始时间为时间vi的最早发生时间  
            l = Vl[k] - p->info;             //最迟开始时间  
            //char tag = (e == l) ? '*' : ' '; //关键活动就是没有缓冲时间,即最早开始时间和最长开始时间相等  
            if(e == l)
				printf("(%c, %c), e = %2d, l = %2d\n", G.vex[i].data, G.vex[k].data, e, l);  
            p = p->nextarc;
        }
	}  
}

int main() { 
	Graph G;
	CreateGraph(G);		//创建邻接表 
	Topology(G);		//进行拓扑排序
	CriticalPath(G);
	return 0;
}


//进行了拓扑排序
//带权值的拓扑排序就是求关键路径 

测试数据:


6 8
123456
1 2 3
1 3 2
2 4 2
2 5 3
3 4 4
3 6 3
4 6 2
5 6 1

猜你喜欢

转载自blog.csdn.net/qq_40830622/article/details/80832835