DP_DAG最长路径_关键路径

DAG最长路

DAG指的是有向无环图, 求它的最长路径有一种DP方法.

我们建立一个数组dp[n+1], dp[i]代表从这个结点出发的最长路径, 那么要求出dp[i], 我们只要求出从i出发的下一结点的dp[j], 使得dp[i] = max(dp[i], dp[j] + G[i][j]), 这就是它的状态转移方程.

可是dp[j] 怎么求呢? 同理,我们如果求出它的后一个结点的dp[k], 我们就可以相应求出dp[j].

经过上面的分析, 不难发现, 整个的过程可以用递归实现. 当i结点的出度为0时, 我们就可以给出dp[i] = 0, 从而实现整个问题的求解.

并且要注意的是, 只对一个节点进行递归求解是不行的, 因为有其他入度为0的结点将不会被考虑进去. 我的理解是, 找出所有入度为0的结点i, 然后solve(i)即可.

完整代码如下

//DAG最短路问题, 动态规划算法, 所有路径里面最长的 
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int maxn = 105;
const int INF = 0x3f3f3f3f;
int G[maxn][maxn] = {};
int dp[maxn] = {}, n, in[maxn] = {};  // 作为结果的数组

int solve(int id)
{
    if (dp[id] > 0) return dp[id];
    for (int i = 1; i <= n; ++i) {
        if (G[id][i] != INF) dp[id] = max(dp[id], solve(i) + G[id][i]);
    }
    return dp[id];
}

int main()
{
    memset(G, 0x3f, sizeof(G));
    int m, x, y, z;
    cin >> n >> m;
    for (int i = 0; i < m; ++i) {
        cin >> x >> y >> z;
        G[x][y] = z;  // 存好图 
        in[y]++;  // 入度 
    }
    for (int i = 1; i <= n; ++i) {
        if (in[i] == 0) solve(i);  // 不必所有的, 只需找到入度为0 的结点. 
    }
    for (int i = 1; i <= n; ++i) {
        cout << i << ':' << dp[i] << '\n';
    }
    cout << endl;
}
/*
9 12
1 3 2
3 6 3
6 8 2
1 4 2
4 6 3
6 9 3
2 4 3
4 7 2
2 5 2
5 7 1
7 9 2
7 8 2

9
*/

DAG最长路径

上面的代码我们可以求得最长路径的值, 但是我们想要求解路径该怎么办呢? 只要稍微改动一点, 在dp的过程中存储下一个结点, 最后获取路径即可.

#include <iostream>
#include <cstring>
using namespace std;

const int maxn = 105;
const int INF = 0x3f3f3f3f;
int G[maxn][maxn], n, choice[maxn] = {};
int dp[maxn] = {}, in[maxn] = {};

int solve(int i)
{
    if (dp[i]) return dp[i];
    for (int j = 1; j <= n; ++j) {
        if (G[i][j] != INF) {
            if (dp[i] < solve(j) + G[i][j]) {
                dp[i] = solve(j) + G[i][j];
                choice[i] = j;
            }
        }
    }
    return dp[i];
}

void getPath(int k)
{
    cout << "DAG最长路 : " << dp[k] << endl << "路径为: "; 
    while (k != 0) {
        cout << k << " ";
        k = choice[k];
    }
    cout << endl;
}

int main()
{
    memset(G, 0x3f, sizeof(G));
    int m, x, y, z, it = -1;
    cin >> n >> m;
    for (int i = 0; i < m; ++i) {
        cin >> x >> y >> z;
        in[y] ++;
        G[x][y] = z;
    }
    for (int i = 1; i <= n; ++i) {
        if (!in[i]) solve(i);
    }
    for (int i = 1; i <= n; ++i) {
        if (dp[0] < dp[i]) {
            it = i;
            dp[0] = dp[i];
        }
    }
    getPath(it);
}
/*
9 12
1 3 2
3 6 3
6 8 2
1 4 2
4 6 3
6 9 3
2 4 3
4 7 2
2 5 2
5 7 1
7 9 2
7 8 2
*/

以i为终点的最长路径

上面两个都是以i为出发点的最长路径, 如果是以i为结尾的最长路径呢? 其实也是差不多的.

#include <iostream>
#include <cstring>
using namespace std;

const int INF = 0x3f3f3f3f;
const int maxn = 105;
int G[maxn][maxn], n, last[maxn] = {};
int out[maxn] = {}, dp[maxn], vis[maxn] = {};

int solve(int i)
{
    if (vis[i]) return dp[i];
    vis[i] = true;
    for (int j = 1; j <= n; ++j) {
        if (G[j][i] != INF) {
            if (dp[i] < G[j][i] + solve(j)) {
                dp[i] = G[j][i] + solve(j);
                last[i] = j;
            }
        }
    }
    return dp[i];
}

void getPath(int k)
{
    cout << "DAG最长路长: " << dp[k] << "\n路径: ";
    while (k != 0) {
        cout << k << ' ';
        k = last[k];
    }
}

int main()
{
    memset(G, 0x3f, sizeof(G));
    memset(dp, 0x3f, sizeof(dp));
    for (int i = 0; i < maxn; ++i)
        dp[i] = 0;
    int m, x, y, z, k = 0;
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        cin >> x >> y >> z;
        G[x][y] = z;
        out[y] ++;
    }
    for (int i = 1; i <= n; ++i) {
        if (out[i]) solve(i);
    }
    for (int i = 1; i <= n; ++i) {
        if (dp[i] > dp[0]) {
            dp[0] = dp[i];
            k = i;
        }
    }
    cout << "k = " << k << endl;
    for (int i = 1; i <= n; ++i) {
        cout << i << " : " << dp[i] << endl;
    }
    getPath(k);
}
/*
9 12
1 3 2
3 6 3
6 8 2
1 4 2
4 6 3
6 9 3
2 4 3
4 7 2
2 5 2
5 7 1
7 9 2
7 8 2
*/

关键路径

求DAG最长路也可以用关键路径的方法求解.

其核心思路是:

有两组数组, 分别为ve[n], 节点的最早时间, vl[n]结点的最晚时间.
e[n], 边的最早时间, l[n], 边的最晚时间.

在实际应用中结点代表事件, 而边代表活动. 活动是需要时间的.

ve和e的关系是: e[i] = e[j], j为与i指向的下一个结点.
vl和l的关系是: l[i] = l[j] - w[i][j], j为i指向的下一个结点.

当e[i] 和 v[i]相等, 那么i就是关键路径上的结点.

这种关键路径可以有很多条, 最后可以使用dfs遍历出来. 不过我写的代码这个过程不知道那里出错了…

用这种关键路径的方法求最长路相比于动态规划方法比较麻烦. 不过其实求最长路的长度, 只是这个方法中的前一部分就可以求解了.

#include <iostream>
#include <cstring>
#include <stack>
#include <vector>
#include <queue>
using namespace std;

struct Node {
    int v, w; // v means the next node, and w means the value of the edge
    Node (int y, int z) : v(y), w(z){}
};

// how to initialize these arries?
const int maxn = 105, INF = 0x3f3f3f3f;
int ve[maxn] = {}, vl[maxn], n;
int e[maxn], l[maxn], inDeg[maxn] = {};
vector<Node> G[maxn];
stack<int> topOrder; // the opposite topology order of stack
queue<int> topSort; // the topology order of queue
vector<int> path[maxn]; // dfs the path 

void getVe()
{
    memset(ve, 0, sizeof(ve)); // initialize to zero
    for (int i = 0; i < n; ++i) {
        if (inDeg[i] == 0) {
            topSort.push(i);
            topOrder.push(i);
        }
    }
    while (!topSort.empty()) {
        int d = topSort.front();
        topSort.pop();
        for (int i = 0; i < G[d].size(); ++i) {
            int v = G[d][i].v;
            ve[v] = max(ve[v], ve[d] + G[d][i].w);
            cout << "v = " << v << "  ve[v] = " << ve[v] << endl;
            inDeg[v]--;
            if (inDeg[v] == 0) {
                topSort.push(v);
                topOrder.push(v);
            }
        }
    }
}

void getVl()
{
    while (!topOrder.empty()) {
        int d = topOrder.top();
        topOrder.pop();
        for (int i = 0; i < G[d].size(); ++i) {
            int v = G[d][i].v;
            vl[d] = min(vl[d], vl[v] - G[d][i].w);
        }
    }
}

void getImport() // get critical path
{
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j < G[i].size(); ++j) {
            int v = G[i][j].v;
            int e = ve[i], l = vl[v] - G[i][j].w;
            if (e == l) path[i].push_back(v);
        }
    }
}

void dfs(int u)
{
    bool flag = 0;
    for (int i = 0; i < G[u].size(); ++i) {
        //if (u == 1) cout << "u == 1\n";
        int v = path[u][i];
        flag = 1;
        cout << u << "--->";
        dfs(v);
    }
    if (flag == 0) cout << u << endl;
}

int main()
{
    int x, y, z, m, ans = 0;
    cin >> n >> m;
    for (int i = 0; i < m; ++i) {
        cin >> x >> y >> z;
        G[x].push_back(Node(y, z)); // save the graphic
        inDeg[y]++; // in degrees 
    }
    getVe();
    cout << "ve[1...n] = ";
    for (int i = 1; i <= n; ++i) {
        ans = max(ans, ve[i]);
        cout << ve[i] << ' ';
    }
    cout << endl;
    cout << "最长路径为 " << ans << endl; 
    fill(vl, vl + n + 1, ans);
    getVl();
    cout << "vl[1...n] = ";
    for (int i = 1; i <= n; ++i) {
        cout << vl[i] << ' ';
    }
    cout << endl;
    getImport();
    /*for (int i = 1; i <= n; ++i) {
        if (vl[i] == 0 && ve[i] == 0) dfs(i);
    }*/
    cout << "test: \n";
    for (int i = 1; i <= n; ++i) {
        cout << "id = " << i << "  :  ";
        cout << path[i].size();
        cout << endl;
    }
    dfs(1); // 不知道最后遍历关键路径图的过程中出了什么问题 
}
/*
6 6
1 2 30
1 4 40
2 3 50
4 5 80
5 6 20
3 5 70

6 6
1 2 30
1 4 70
2 3 50
4 5 80
5 6 20
3 5 70
*/
  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值