图论 重写拓扑排序+关键路径


/*
严格参照《数据结构(严蔚敏版)》
2017.12.6
by kk
*/

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<cmath>
#include<algorithm>
#define infinity 0x3f3f3f3f
#define maxsize 100
#define OK 1
#define ERROR 0
using namespace std;
typedef int VertexType ;
typedef struct NODE//边结点的定义
{
    VertexType adjvex;//结点
    int weight ;//此弧的权值
    NODE *next;
} Node;

typedef struct//邻接表头的定义
{
    int in;//用来记录顶点的入度
    VertexType data;
    NODE *first;
} nodeList;

typedef struct
{
    int num_vex,num_edge;//结点个数,边的条数
    VertexType vex[maxsize];//结点集
    nodeList headvex[maxsize];//邻接表
} GraphL;

void create(GraphL &g)//邻接表的创建
{
    int i,j,k;
    int x,y,z;
    NODE *e;
    scanf("%d%d",&g.num_vex,&g.num_edge);

    for(i=0; i<g.num_vex; i++)//邻接表的初始化
    {
        scanf("%d",&g.headvex[i].data);
        g.headvex[i].in=0;//每个结点的入度为0
        g.headvex[i].first=NULL;//邻接表头的next为空
    }

    for(i=0; i<g.num_edge; i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        e=(NODE*)malloc(sizeof(NODE));
        e->weight=z;
        e->adjvex=y;
        //建立的是有向图
        e->next=g.headvex[x].first;//头插法插入新的结点
        g.headvex[x].first=e;
        g.headvex[y].in++;
    }
}
int stack2[maxsize],top2;
int *etv,*ltv;//分别是储存结点事件最早和最晚开始的时间的数组-
int TopologicalOrder(GraphL g)
{
    int stack1[maxsize],top1=0;
    top2=0;
    //stack1是拓扑排序的时候用,用来储存入度为0的结点下标
    //stack2是寻找顶点时间最晚开始时间(ltv)的时候用,其实就是将按拓扑排序时遍历的结点的次序塞到stack2里
    /*
    也就是说求ltv时遍历结点的顺序刚好跟求etv时遍历结点的顺序相反
    拓扑排序时遍历结点的顺序为正,ltv时的遍历结点的顺序为反
    */
    int i,j,Count=0,k;
    etv=(int *)malloc(g.num_vex*sizeof(int ));
    ltv=(int *)malloc(g.num_vex*sizeof(int ));
    if(etv==NULL||ltv==NULL)return ERROR;


    memset(etv,0,g.num_vex*sizeof(int));
    memset(ltv,0,g.num_vex*sizeof(int));
    memset(stack1,0,sizeof(stack1));
    memset(stack2,0,sizeof(stack2));
    /*
    !!!!!!!!!!!!!!!!!!!!!!!!!!
    这里最重要(不是关于此算法的),如果知道C的这个知识点的请跳过。
    memset()函数注意事项:
    1.memset(a,0,sizeof(a));
      memset(a,-1,sizeof(a));
      以上两个当a是字符数组或者int数组都好使
    2.memset(a,-1,sizeof(a));如果a是int数组不好使
    3. memset(a,0,sizeof(a))当a是指针的时候就得注意了,若果是用malloc函数分配空间后的,
       memset(a,0,sizeof(a))只会初始化a这个空间内的值为0,但并不会连同malloc函数分配的空间全都初始化为0
       !!!
       故正确的写法是memset(a,0,a分配空间的个数*sizeof(a))或者 memset(a,0,a分配的个数*sizeof(int))
    */

    //先将入度为0的结点入栈
    for(i=0; i<g.num_vex; i++)
        if(g.headvex[i].in==0)stack1[++top1]=i;

//
//      for(i=0; i<g.num_vex; i++)
//            printf("%d ",etv[i]);
//        puts("");

    int gettop;
    NODE *e;
    while(top1!=0)
    {
        gettop=stack1[top1--];//出栈
        stack2[++top2]=gettop;
        ++Count;
        //如果遍历遍历结束后发现count的值小于结点的总个数,说明图中存在环
        for(e=g.headvex[gettop].first; e; e=e->next)
        {
            k=e->adjvex;
            if((--g.headvex[k].in)==0)//更新结点的入度
            {
                stack1[++top1]=k;//将结点入度为0的点继续入栈
            }
            if((etv[gettop]+e->weight)>etv[k])
            {
                etv[k]=etv[gettop]+e->weight;//修改结点事件最早发生时间
            }
        }
    }
    if(Count<g.num_vex)return ERROR;
    else return OK;
}
void criticalPath(GraphL g)
{
    NODE *e;
    int i,j;
    TopologicalOrder(g);
    for(i=0; i<g.num_vex; i++)
        printf("%d ",etv[i]);
    puts("");
    for(i=0; i<g.num_vex; i++)//初始化ltv全都为活动结束的那个时间
        ltv[i]=etv[g.num_vex-1];

    int gettop,k;//下面还是倒着遍历结点,找ltv数组的值
    while(top2!=0)
    {
        gettop=stack2[top2--];
        for(e=g.headvex[gettop].first; e; e=e->next)
        {
            k=e->adjvex;
            if((ltv[k]-e->weight)<ltv[gettop])//k是弧头,gettop是弧尾(也就是说这条边是从gettop指向k的)
                ltv[gettop]=ltv[k]-e->weight;

        }
    }
    //找结点事件最早开始时间etv是找最大值,找结点事件最晚发生时间ltv是找最小值
    int ete,lte;//分别代表弧最早开始时间和最晚开始时间
    for(j=0; j<g.num_vex; j++)
    {

        for(e=g.headvex[j].first; e; e=e->next)
        {
            k=e->adjvex;
            ete=etv[j];
            lte=ltv[k]-e->weight;//边的三元组是(j,k,e->weight)
            if(ete==lte)
            {
                printf("%d--%d %d\n",g.headvex[j].data,g.headvex[k].data,e->weight);
            }
        }
    }
}



int main()
{
    GraphL G;
    create(G);
    criticalPath(G);
    return 0;
}
/*
10 13
0 1 2 3 4 5 6 7 8 9
0 1 3
0 2 4
1 3 5
2 3 8
2 5 7
1 4 6
3 4 3
4 6 9
4 7 4
5 7 6
6 9 2
7 8 5
8 9 3
*/







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值