(计科女孩摸鱼日常)数据结构之关键路径

这篇博客详细介绍了如何通过邻接链表构建图,并计算关键路径。内容包括读取图信息、计算顶点入度、寻找起点、确定最早和最晚发生时间,以及生成关键路径的过程。同时,博主分享了在实现过程中遇到的问题和解决方案。
摘要由CSDN通过智能技术生成

好久没有写了呀...

这些代码都是同组的万福同学一步一步教我们打的,向大佬致敬!!!

先来张图

图来了就要读图呀 怎么读图呢 首先要建图

我们使用邻接链表的存储方式 于是有了以下两个结构体

struct edge{
    int end;
    int weight;
    int Ee;
    int El;
    edge * next_edge;
};
struct vertex{
    int ind;
    int Ve;
    int Vl;
    edge * first_edge;
};

注意!结构体edge要写在vertex前面 否则 你将获得以下报错

因为它找不到啊!!

这里建图的逻辑是:图是由顶点vertex构成的,而点与点之间是通过边edge连接起来的

所以 这张图G的类型为vertex 于是我们在主函数中写上

int main(){
    vertex G[100];
    return 0;
}

 图建好了 我们就开始读图吧!

输入关键信息:图的顶点数n以及边数m

9 11

怎么读图呢 输入边两端的顶点以及这条边的权值

1 2 6

1 3 4

1 4 5

2 5 1

3 5 1

4 6 2

5 7 9

5 8 7

6 8 4

7 9 2

8 9 2

等一下!万一是垃圾数据怎么办!!!我们先养好习惯 初始化一下!!

首先初始化顶点

for (int i = 1; i <= n; ++i) {
        G[i].ind = 0;
        G[i].first_edge = NULL;
    }

由于存图暂时用不上顶点的最早和最晚发生时间 我们后面再对Ve和Vl进行初始化

读入顶点和边的信息

    int u,v,w;
    for (int i = 0; i < m; ++i) {
        edge * now = new edge;
        cin>>u>>v>>w;
        now->end = v;
        now->weight = w;
        now->next_edge = G[u].first_edge;
        G[u].first_edge = now;
    }

其中进行了一个头插入(链表的知识 不会的好好反思一下!)

(在我自己写的时候一直有个疑问 这个边只有终点end的信息 那起点存在哪呢 看到这段代码 你懂了吗)

———————————————读图这项简单的小任务完成啦——————————————

记得我们的struct vertex里面有个ind吧 这个是入度 需要我们自己算 接下来很快就要用到入度这个信息了

算入度!!

    for (int i = 1; i <= n; ++i) {
        edge * p = G[i].first_edge;
        while (p){
            G[p->end].ind++;
            p = p->next_edge;
        }
    }

我觉得还挺好理解的 都是套路了

用一个结构体指针p来试探:“有没有边?”

“有!”

那么这条边的终点的入度就+1

“下一条边!”

...

算完了 就是这么简单易懂

——————————————即将进入重头戏!!!——————————————————

在好戏开始之前 我们先准备好要用到的工具——栈和队列

int que[100],head = 0,tail = 0;
int sta[100],top = 0;

(栈和队列中存的都是顶点的编号)

关键路径的要点就是 最早最晚

最早:从左往右算 入度先为0 先有最早发生时间

最晚:从右往左算 越是靠近终点 越早算出最晚发生时间

记得我们前面还没有对这几个变量进行初始化 为了防止垃圾数据 我们养成好习惯 初始化一下

    for (int i = 1; i <= n; ++i) {
        G[i].Ve = 0;
        G[i].Vl = INT_MAX;
    }
    for (int i = 1; i <= n; ++i) {
        edge * p = G[i].first_edge;
        while (p){
            p->Ee = 0;
            p->El = INT_MAX; 
            p = p->next_edge;
        }
    }

——————————————重头戏开始了!!!————————————————————

找起点

    for (int i = 1; i <= n; ++i) {
        if (G[i].ind == 0){
            que[tail++] = i;
        }
    }

算出顶点和边的最早发生时间 

    while (head < tail){
        //出队 入栈
        int now = que[head++];
        sta[top++] = now;
        
        //存入Ee和Ve后 解除顶点与边的束缚
        edge * p = G[now].first_edge;
        while (p){
            //存入Ee和Ve
            p->Ee = G[now].Ve;
            G[p->end].Ve = max(G[p->end].Ve,G[p->end].Ve+p->weight);
            //解除束缚 即入度-1
            G[p->end].ind--;
            //解除束缚后 判断是否可以入队
            if (G[p->end].ind == 0){
                que[tail++] = p->end;
            }
            //下一条边
            p = p->next_edge;
        }
    }

此时 我们看一下此题中的队列的存入情况

 没错 是一样的 那我们为什么要多次一举 建立队列了之后还要建立栈呢?带着这个疑问 我们继续

(其实现在 我们已经存好了顶点和边的最早完成时间了 还能明显地看到一次次刷新的过程)

仔细体会下神奇的while语句 

算出最晚发生时间的准备步骤

生成关键路径Critical Path

建立一个结构体来保存关键路径的信息

struct path{
    int u;
    int v;
    int w;
};

主函数中

path CriticalPath[100];

找出终点terminus

int terminus = que[tail-1];

此处注意!为什么要tail-1?

记得我们每次入队都要tail++吗 也就是说que[tail]里面是垃圾数据(如果没有初始化的话)真正存了数据的是0~tail-1个坑

算出工程周期T

    int terminus = que[tail-1];
    int T = G[terminus].Ve;
    G[terminus].Vl = T;

算最晚发生时间

    while (top){
        int now = sta[--top];
        edge * p = G[now].first_edge;
        while (p){
            p->El = G[p->end].Vl - p->weight;
            G[now].Vl = min(G[now].Vl,G[p->end].Vl - p->weight);
            if (p->Ee == p->El){
                path cp;
                cp.u = now;
                cp.v = p->end;
                cp.w = w;
                CriticalPath[index++] = cp;
            }
            p = p->next_edge;
        }
    }

完整代码

#include <iostream>
using namespace std;
struct edge{
    int end;
    int weight;
    int Ee;
    int El;
    edge * next_edge;
};
struct vertex{
    int ind;
    int Ve;
    int Vl;
    edge * first_edge;
};
struct path{
    int u;
    int v;
    int w;
};
int main(){
    vertex G[100];
    int n,m;
    puts("请输入顶点数n以及边数m:");
    cin>>n>>m;
    for (int i = 1; i <= n; ++i) {
        G[i].ind = 0;
        G[i].first_edge = NULL;
    }
    int u,v,w;
    for (int i = 0; i < m; ++i) {
        edge * now = new edge;
        cin>>u>>v>>w;
        now->end = v;
        now->weight = w;
        now->next_edge = G[u].first_edge;
        G[u].first_edge = now;
    }

    for (int i = 1; i <= n; ++i) {
        edge * p = G[i].first_edge;
        while (p){
            G[p->end].ind++;
            p = p->next_edge;
        }
    }

    int que[100],head = 0,tail = 0;
    int sta[100],top = 0;
    for (int i = 1; i <= n; ++i) {
        G[i].Ve = 0;
        G[i].Vl = INT_MAX;
    }
    for (int i = 1; i <= n; ++i) {
        edge * p = G[i].first_edge;
        while (p){
            p->Ee = 0;
            p->El = INT_MAX;
            p = p->next_edge;
        }
    }

    for (int i = 1; i <= n; ++i) {
        if (G[i].ind == 0){
            que[tail++] = i;
        }
    }

    while (head < tail){
        //出队 入栈
        int now = que[head++];
        sta[top++] = now;

        //存入Ee和Ve后 解除顶点与边的束缚
        edge * p = G[now].first_edge;
        while (p){
            //存入Ee和Ve
            p->Ee = G[now].Ve;
            G[p->end].Ve = max(G[p->end].Ve,G[now].Ve+p->weight);
            //解除束缚 即入度-1
            G[p->end].ind--;
            //解除束缚后 判断是否可以入队
            if (G[p->end].ind == 0){
                que[tail++] = p->end;
            }
            //下一条边
            p = p->next_edge;
        }
    }

    path CriticalPath[100];
    int index = 0;
    int terminus = que[tail-1];
    int T = G[terminus].Ve;
    G[terminus].Vl = T;

    while (top){
        int now = sta[--top];
        edge * p = G[now].first_edge;
        while (p){
            p->El = G[p->end].Vl - p->weight;
            G[now].Vl = min(G[now].Vl,G[p->end].Vl - p->weight);
            if (p->Ee == p->El){
                path cp;
                cp.u = now;
                cp.v = p->end;
                cp.w = w;
                CriticalPath[index++] = cp;
            }
            p = p->next_edge;
        }
    }

    for (int i = 0; i < index; ++i) {
        cout<<CriticalPath[i].u<<" "<<CriticalPath[i].v<<" "<<CriticalPath[i].w<<endl;
    }
    return 0;
}

具体理解我后面再来补充!!!

(肝了两个小时 写累了......)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值