【id:245】【10分】D. 关键路径-STL版 我用的参考代码

1.题目描述

给定有向图无环的边信息,求每个顶点的最早开始时间、最迟开始时间。

// 参考代码

#include <iostream>
#include <vector>
#include <string>
#include <queue>
using namespace std;

class Vertex {
public:
    int indexNo;
    bool hasEnterQueue;
    int early;
    int later;

    Vertex(int indexNo) {
        this->indexNo = indexNo;
        this->hasEnterQueue = false;
        early = -1;
        later = 0x7FFFF;
    }
    void updateEarly(int parentEarly, int edgeValue) {
        int newEarly = parentEarly + edgeValue;
        if (newEarly > this->early)
            this->early = newEarly;
    }
    void updateLater(int childLater, int edgeValue) {
        int newLater = childLater - edgeValue;
        if (newLater < this->later)
            this->later = newLater;
    }
};


class Graph {
public:
    vector<Vertex> vertexes;
    vector<vector<int> > adjMat;
    int n;
public:
    void readVertexes() {
        //TODO: 将顶点数读入成员变量n
        
        //TODO: 从输入初始化vertexes数组
        int i=0;
        for(; i<n; ++i) {
            Vertex v(i);
            this->vertexes.push_back(v);
        }
        
        //为成员变量adjMat创建内存,赋初值
        for(i=0; i<n; ++i) {
            vector<int> row;
            int j=0;
            for(; j<n; ++j) {
                //TODO: 将0增加到row最后
            }
           //TODO: 将row增加到adjMat最后
        }
    }
    void readAdjMatrix() {
        //read the adjacent info into this->adjMat
        int edges;
        cin >> edges;
        int i=0;
        int s, t, w;  //s源顶点编号,t目的顶点编号,w边长
        for(; i<edges; ++i) {
            //TODO: 读入s,t,w,并将adjMat的第s行、第t列的值改为w.
        }
    }

    void updateEarly(int parentNo, queue<int>& earlyQue) {
        int parentEarly = vertexes[parentNo].early;  //读入父结点early值

        int j=0;
        for(; j<n; ++j) {
            int edgeValue = adjMat[parentNo][j];
            if (edgeValue == 0) continue;  //若父结点与结点j没有边相连,pass

            Vertex& child = vertexes[j];
            child.updateEarly(parentEarly, edgeValue); //更新子结点j的early信息

            if(!child.hasEnterQueue) {
                child.hasEnterQueue = true; //将子结点加入队列
                earlyQue.push(j);
            }
        }
    }
    void updateLater(int childNo, queue<int>& laterQue) {
        //TODO:
    }

    int getRoot() {
        //获取入度为0的顶点
        int j=0;
        for(; j<n; ++j) {
            int i=0;
            for(; i<n && adjMat[i][j] == 0; ++i);
            if (i>=n) return j; //j has not any in-edges.
        }
        return -1;  //表示没找到
    }
    int getLeaf() {
        //TODO: 获取出度为0的顶点
    }

    void printEarlyLater(bool isEarly) {
        int i=0;
        for(; i<n; ++i) {
            Vertex& v = vertexes[i];
            if (isEarly)
                cout << v.early << " ";
            else {
                cout << v.later << " ";
            }
        }
        cout << endl;
    }

    void findEarly() {
        //执行关键路径算法,求每个顶点的最早开始时间。
        int r = getRoot();
        Vertex& root = vertexes[r];
        root.hasEnterQueue = true;
        root.early = 0;

        queue<int> que;
        que.push(r);

        while(!que.empty()) {
            int p = que.front();
            que.pop();

            updateEarly(p, que);
        }

        printEarlyLater(true);
    }
    void clearEnterQueue() {
        int i=0;
        for(; i<n; ++i) {
            vertexes[i].hasEnterQueue = false;
        }
    }
    void findLater() {
        //TODO:调用clearEnterQueue,以清除每个顶点的hasEnterQueue=false
        //执行关键路径算法,求每个顶点的最迟开始时间。
    }

    void main() {
        readVertexes();
        readAdjMatrix();
        findEarly();
        findLater();
    }
};


int main() {
    int t=1;
    //cin >> t;
    while (t--) {
        Graph g;
        g.main();
    }
    return 0;
}

输入

第一行图的顶点总数

第二行边的总数

第三行开始,每条边的时间长度,格式为源结点   目的结点   长度

输出

第一行:第个顶点的最早开始时间

第二行:每个顶点的最迟开始时间

输入样例

9
12
0 1 3
0 2 10
1 3 9
1 4 13
2 4 12
2 5 7
3 6 8
3 7 4
4 7 6
5 7 11
6 8 2
7 8 5

输出样例

0 3 10 12 22 17 20 28 33 
0 9 10 23 22 17 31 28 33 

——————————————————————————————

2.正文 —— 参考代码确实是错的

这道题的代码确实是错的,这道题的解法是拓扑排序,入度为零的才会更新下一节点,而void updateEarly()会把摸到的节点直接放进那个队列

举个例子:这个图就两条路,一条路上100个点,另一条路直接到终点前一个点。题目的代码开局就会把终点前一个点放进队列,假如终点后面还有个点,那就算100个点过来,也不会过去了。  如图:

3.修改代码思路 

其实知道是拓扑排序就很简单了,自己去写就好了。

多起点和多终点其实我一开始就写了,但我其实当时不知道这道题是干什么😚(想速通,懒得看,老师上课讲这个了,不过我忘了,和课组长交流后才知道惹。早点知道的话,写多起点多终点的时候应该能更早发现)。

小tips:(其实不用看,就是我的代码写法)

因为起始点不一定只有一个(终点同理),所以getroot得加条件。然后发现拓扑排序要找入度为0的点嘛,所以也可以用这个函数。但是后面的点的early已经被弄好了,所以需要另外把起始点们的early初始化一下子。

我这边大胆舍弃毫无用处的队列(这道题代码是遍历找入度为0,所以我就不需要队列存入度为0的点了。当然这样做效率低),然后废物利用Vertex().hasEnterQueue和clearEnterQueue()。

4.AC代码

#include <iostream>
#include <vector>
#include <string>
#include <queue>
using namespace std;

class Vertex {
public:
    int indexNo;
    bool hasEnterQueue;
    int early;
    int later;

    Vertex(int indexNo) {
        this->indexNo = indexNo;
        this->hasEnterQueue = false;
        early = -1;
        later = 0x3f3f3f3f;//0x7FFFF;
    }
    void updateEarly(int parentEarly, int edgeValue) {
        int newEarly = parentEarly + edgeValue;
        if (newEarly > this->early)
            this->early = newEarly;
    }
    void updateLater(int childLater, int edgeValue) {
        int newLater = childLater - edgeValue;
        if (newLater < this->later)
            this->later = newLater;
    }
};


class Graph {
public:
    vector<Vertex> vertexes;
    vector<vector<int> > adjMat;
    vector<vector<int> > tmpadjMat;
    int n;
    int latest;
public:
    void readVertexes() {
        //TODO: 将顶点数读入成员变量n
        cin >> n;
        //TODO: 从输入初始化vertexes数组
        int i = 0;
        for (; i < n; ++i)
        {
            Vertex v(i);
            this->vertexes.push_back(v);
        }

        //为成员变量adjMat创建内存,赋初值
        for (i = 0; i < n; ++i)
        {
            vector<int> row;
            int j = 0;
            for (; j < n; ++j)
            {
                //TODO: 将0增加到row最后
                row.push_back(0);//666
            }
            //TODO: 将row增加到adjMat最后
            adjMat.push_back(row);
        }
    }
    void readAdjMatrix() {
        //read the adjacent info into this->adjMat
        int edges;
        cin >> edges;
        int i = 0;
        int s, t, w;  //s源顶点编号,t目的顶点编号,w边长
        for (; i < edges; ++i) {
            //TODO: 读入s,t,w,并将adjMat的第s行、第t列的值改为w.
            cin >> s >> t >> w;
            adjMat[s][t] = w;
            //adjMat[t][s] = w;
        }
        tmpadjMat = adjMat;
    }
    //
    void updateEarly(int parentNo) {
        int parentEarly = vertexes[parentNo].early;  //读入父结点early值

        int j = 0;
        for (; j < n; ++j) {
            int& edgeValue = adjMat[parentNo][j];
            if (edgeValue == 0) continue;  //若父结点与结点j没有边相连,pass

            Vertex& child = vertexes[j];
            child.updateEarly(parentEarly, edgeValue); //更新子结点j的early信息

            edgeValue = 0;
        }
    }
    void updateLater(int childNo) {
        //TODO:
        int childLater = vertexes[childNo].later;  //读入父结点early值

        int j = 0;
        for (; j < n; ++j) {
            int& edgeValue = adjMat[j][childNo];
            if (edgeValue == 0) continue;  //若父结点与结点j没有边相连,pass

            Vertex& child = vertexes[j];
            child.updateLater(childLater, edgeValue); //更新子结点j的early信息

            edgeValue = 0;
        }
    }

    int getRoot() {
        //获取入度为0的顶点
        int j = 0;
        for (; j < n; ++j) {
            int i = 0;
            for (; i < n && adjMat[i][j] == 0; ++i);
            if (i >= n && vertexes[j].hasEnterQueue == false) return j; //j has not any in-edges.
        }
        return -1;  //表示没找到
    }
    int getLeaf() {
        //TODO: 获取出度为0的顶点
        int j = 0;
        for (; j < n; ++j) {
            int i = 0;
            for (; i < n && adjMat[j][i] == 0; ++i);
            if (i >= n && vertexes[j].hasEnterQueue == false) return j;
        }
        return -1;  //表示没找到
    }

    void printEarlyLater(bool isEarly) {
        int i = 0;
        for (; i < n; ++i) {
            Vertex& v = vertexes[i];
            if (isEarly)
            {
                cout << v.early << " ";
            }
            else
            {
                cout << v.later << " ";
            }
        }
        cout << endl;
    }

    void findEarly() {
        //执行关键路径算法,求每个顶点的最早开始时间。

        int r = getRoot();
        while (r != -1)
        {
            Vertex& root = vertexes[r];
            root.hasEnterQueue = true;
            root.early = 0;

            r = getRoot();
        }

        clearEnterQueue();
        r = getRoot();
        while (r != -1)
        {

            Vertex& root = vertexes[r];
            root.hasEnterQueue = true;



                updateEarly(r);

            r = getRoot();
        }

        printEarlyLater(true);
    }
    void clearEnterQueue() {
        int i = 0;
        for (; i < n; ++i) {
            vertexes[i].hasEnterQueue = false;
        }
    }
    void findLater() {
        //TODO:调用clearEnterQueue,以清除每个顶点的hasEnterQueue=false
        clearEnterQueue();
        adjMat = tmpadjMat;
        //执行关键路径算法,求每个顶点的最迟开始时间。

        int r = getLeaf();
        while (r != -1)
        {
            Vertex& root = vertexes[r];
            root.hasEnterQueue = true;
            root.later = root.early;

            r = getLeaf();
        }
        clearEnterQueue();
        r = getLeaf();
        while (r != -1)
        {

            Vertex& root = vertexes[r];
            root.hasEnterQueue = true;

            updateLater(r);

            r = getLeaf();
        }

        printEarlyLater(false);
    }

    void main() {
        readVertexes();
        readAdjMatrix();
        findEarly();
        findLater();
    }
};


int main() {
    int t = 1;
    //cin >> t;
    while (t--) {
        Graph g;
        g.main();
    }
    return 0;
}

5.故事

我以为参考代码是对的,然后对称写法,结果只能过两个样例,反复检查代码是没有错误的,再加上对了两个样例,有点自信,然后就去班群里问助教了,助教甩个后台输出就不管了(应该是老师不让多看):

然后今晚打了会CSGO,本来想学动归,可是就想过掉这个简单的拓扑。回来又读了一遍,确信没有问题后,再看了看这个输出:

才想到这个就压根不是拓扑,甚至就是啥也不是,就是错滴。(其实一开始助教发了的时候我只发现有负的这个问题,但是根据这道题拓扑的逻辑根本不会有负的!而我又是封装的代码,最多调用失误,然后调用反复检查没有失误,所以就很疑惑)

网上搜了,没有用本题代码的解法。所以也挺想搞下的。老师们不负责的话,希望学弟们不会因为踩到这个坑而浪费太多时间。(不会吧不会吧就我一个没发现这道题拓扑是错的)

😘

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值