1. 程式人生 > 其它 >資料結構 _ 基礎練習 _ 7-12 How Long Does It Take _ 遞迴解法及非遞迴解法

資料結構 _ 基礎練習 _ 7-12 How Long Does It Take _ 遞迴解法及非遞迴解法

技術標籤:資料結構_基礎

資料結構練習 —— How Long Does It Take

原題

點此連結1

遞迴解法

解題分析

參考課本(高等教育出版社 - 陳越 - 《資料結構》)P250關於AOE的描述

  1. 遞迴演算法的核心:
// 假若 v 是 結點 w 的前向鄰接點,即有 v->w
// 對於 w 的每一個前向結點v,則應有如下式子
Time[w] = max(Time[v]+ E(v->w))
遞迴演算法就是從此開始,計算當前結點的每一個前向結點的最長路徑,如若前向結點是起點,則等於路經長。
  1. 如何判別是否構成環路
    對於每一個計算點,可以設定一個“待計算標記集合”,如果在遞迴過程中發現待計算的結點是屬於“待計算集合”中的結點,那麼此時必然已構成迴路
  2. 如何判別是否是連通圖
    較為困難

程式碼

/**
 * @file How Long Does It Take.cpp
 * @author your name ([email protected])
 * @brief 7-12 How Long Does It Take (25 分)
 *        address: https://pintia.cn/problem-sets/16/problems/674
 * @version 0.1
 * @date 2021-02-06
 * 
 * @copyright Copyright (c) 2021
 * 
 */
#include <iostream> #include <vector> #include <string> #include <algorithm> #include <cmath> using namespace std; /* * 解題思路 * 需要求取關鍵路徑的長度 * 使用帶權值判斷的bfs搜尋演算法 * 判斷有無環路 * (判斷圖是否連通:未判斷) * * 方案1 遞迴演算法求取關鍵路徑長 * 起始點定義為0 * earliest[s] = 0 * 對w的每個指向ta的點v * earliest[w] = max_(v->w){earliest[w], earliest[v] + c(v->w)} * * 初始化部分 * 1.基礎資料,與以往不同,這邊需要的是指向點w的所有點v的集合,即需要父結點集,以前往往定義的是子結點集 * 1.獲取起始點 - 入度為 0 的點 * 2.獲取終止點 - 出度為 0 的點 * * 環路判斷 * 定義訪問變數 visited * 計算 點 w -> visited[w] = true * 遞迴計算 * 對於任意點 v,如果visited[v] == true,則必有環路 * * 需要計算 w -...遞迴計算...-> 需要計算 w:存在環路 */
// 獲取基本資料以及起始、終點 vector<vector<pair<int, int>>> rawData; // 反向 vector<int> inDegree; // 入度 vector<int> outDegree; // 出度 vector<int> startPoints; // 起點 vector<int> endPoints; // 終點 int Time(int index, vector<bool> &wait) { if (find(startPoints.begin(), startPoints.end(), index) != startPoints.end()) return 0; // 如果index是等待計算的,那就表明存在環路了 if (wait[index]) return -1; wait[index] = true; vector<int> time; for (auto &r : rawData[index]) { auto res = Time(r.first, wait); if (res == -1) return -1; time.push_back(res + r.second); } wait[index] = false; return *max_element(time.begin(), time.end()); } int main() { int num, edge; // 點數,邊數 cin >> num >> edge; rawData.resize(num); inDegree.resize(num); outDegree.resize(num); for (auto i = 0; i < edge; i++) { int s, d, time; cin >> s >> d >> time; rawData[d].push_back({s, time}); ++inDegree[d]; ++outDegree[s]; } for (auto i = 0; i < num; i++) { if (!inDegree[i]) startPoints.push_back(i); if (!outDegree[i]) endPoints.push_back(i); } if (startPoints.empty() || endPoints.empty()) { cout << "Impossible" << endl; return 0; } vector<bool> waitForCal(num, false); auto maxTime = -1; for (auto r : endPoints) { auto tmp = Time(r, waitForCal); if (tmp == -1) { cout << "Impossible" << endl; return 0; } maxTime = max(maxTime, tmp); } cout << maxTime; return 0; }

非遞迴解法

解題思路

採用非遞迴解法相對遞迴解法要難一些

  1. 演算法核心
    考慮這樣一個情況,考慮起始點S1,隨後將所有其餘起始點擦除(擦除之後,需要保證圖中只有起始點S1),那麼S1中的鄰接點中必然有入度為1的結點S2,此結點S2花費的時間就是邊長。
    類似於dijkstra演算法,這個結點S2已經處理完畢,那麼就需要擦除起始點S1,然後考慮結點S2的鄰接點,以此類推,可以採用BFS的處理方案,設定一個佇列。
  2. 擦除演算法
    假若圖是這樣一個 0 -> 1 -> 2 , 3 -> 4 -> 1,如果只考慮結點0,那就需要擦除結點 3 與結點 4,在擦除結點的同時也需要更新入度。
  3. 判別迴環
    如果在計算過程中碰到了一個鄰接點是已經算好的結點(入度為0),那必然存在環路
  4. 判別連通
    只需要判別每一個終點是否都是初始值即可,值得一提的是本題不能加上這個判斷(不過可能是個人演算法的問題)

程式碼

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <algorithm>

using namespace std;

/*
 * 解題思路
 * 需要求取關鍵路徑的長度
 * 使用帶權值判斷的bfs搜尋演算法
 * 判斷有無環路
 * (判斷圖是否連通:較容易判斷)
 *
 * 方案2 BFS 修改的搜尋演算法
 *
 * 初始化部分
 * 1.基礎資料,與以往不同,這邊需要的是指向點w的所有點v的集合,即需要父結點集,以前往往定義的是子結點集
 * 1.獲取起始點 - 入度為 0 的點
 * 2.獲取終止點 - 出度為 0 的點
 * 
 * 對於每一個起始點S計算
 * 
 * 重新計算入度
 * 將所有其餘起點壓入佇列Q
 * 當佇列非空時
 * {
 *      結點 W 出佇列
 *      對於 W 的每一個鄰結點
 *          入度減一,此後如果入度為0,則需要將W入隊
 * }
 * 
 * 將S壓入佇列Q
 * Time[] = 0
 * 
 * 當佇列不空時:
 * 
 * W = 出列Q
 * 對於W的每一個鄰接點V
 * {
 *      如果 V 未收錄
 *          如果點V的入度為1
 *              Time[V] = max(Time[V], Time[W]+C(W->V))
 *              收錄V
 *              將V壓入佇列
 *          否則
 *              V的入度-1
 *      否則表明存在環路
 * }
 */

int main()
{
    int num, edge; // 點數,邊數
    cin >> num >> edge;

    // 獲取基本資料以及起始、終點
    vector<vector<pair<int, int>>> rawData(num); // 正向資料
    vector<int> inDegree(num);                   // 入度
    vector<int> outDegree(num);                  // 出度
    vector<int> startPoints;                     // 起點
    vector<int> endPoints;                       // 終點

    for (auto i = 0; i < edge; i++)
    {
        int s, d, time;
        cin >> s >> d >> time;
        rawData[s].push_back({d, time});
        ++inDegree[d];
        ++outDegree[s];
    }

    for (auto i = 0; i < num; i++)
    {
        if (!inDegree[i])
            startPoints.push_back(i);
        if (!outDegree[i])
            endPoints.push_back(i);
    }

    if (startPoints.empty() || endPoints.empty())
    {
        cout << "Impossible" << endl;
        return 0;
    }

    // 對於每一個起點
    auto maxTime = 0;
    for (auto &subStr : startPoints)
    {
        // 此時相當於除去其餘起始點,所以需要將其餘起始點的影響消除
        auto inDegreeTmp = inDegree;
        queue<int> q;
        for (auto &r : startPoints)
            if (r != subStr)
                q.push(r);
        while (!q.empty())
        {
            auto index = q.front();
            q.pop();
            for (auto &r : rawData[index])
                if (!--inDegreeTmp[r.first])
                    q.push(r.first);
        }

        q.push(subStr);
        vector<bool> collected(num, false);
        vector<int> time(num, 0);
        collected[subStr] = true;
        while (!q.empty())
        {
            auto index = q.front();
            q.pop();
            // 對於每一個鄰接點
            for (auto &r : rawData[index])
            {
                // 對於每一個未被收錄的鄰接點
                if (!collected[r.first])
                {
                    // 度為1 表明 這是最後一個與之向關聯的點
                    if (inDegreeTmp[r.first] == 1)
                    {
                        collected[r.first] = true;
                        time[r.first] = max(time[r.first], time[index] + r.second);
                        q.push(r.first);
                        // 更新一下最長時間
                        maxTime = max(time[r.first], maxTime);
                    }
                    // 度不為1 則更新度和花費
                    else
                    {
                        --inDegreeTmp[r.first];
                        time[r.first] = max(time[r.first], time[index] + r.second);
                    }
                }
                // 如果存在被收錄的點
                else
                {
                    cout << "Impossible" << endl;
                    return 0;
                }
            }
        }

        // 最後一個測試結點無法通過?
        // for (auto &r : endPoints)
        // {
        //     // 如果存在無法到達的終點,表明圖並不連通
        //     if (!time[r])
        //     {
        //         cout << "Impossible" << endl;
        //         return 0;
        //     }
        // }
    }
    cout << maxTime;

    return 0;
}