資料結構 _ 基礎練習 _ 7-12 How Long Does It Take _ 遞迴解法及非遞迴解法
阿新 • • 發佈:2021-02-08
技術標籤:資料結構_基礎
資料結構練習 —— How Long Does It Take
原題
點此連結1
遞迴解法
解題分析
參考課本(高等教育出版社 - 陳越 - 《資料結構》)P250關於AOE的描述
- 遞迴演算法的核心:
// 假若 v 是 結點 w 的前向鄰接點,即有 v->w
// 對於 w 的每一個前向結點v,則應有如下式子
Time[w] = max(Time[v]+ E(v->w))
遞迴演算法就是從此開始,計算當前結點的每一個前向結點的最長路徑,如若前向結點是起點,則等於路經長。
- 如何判別是否構成環路
- 如何判別是否是連通圖
較為困難
程式碼
/**
* @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;
}
非遞迴解法
解題思路
採用非遞迴解法相對遞迴解法要難一些
- 演算法核心
考慮這樣一個情況,考慮起始點S1,隨後將所有其餘起始點擦除(擦除之後,需要保證圖中只有起始點S1),那麼S1中的鄰接點中必然有入度為1的結點S2,此結點S2花費的時間就是邊長。
類似於dijkstra演算法,這個結點S2已經處理完畢,那麼就需要擦除起始點S1,然後考慮結點S2的鄰接點,以此類推,可以採用BFS的處理方案,設定一個佇列。 - 擦除演算法
假若圖是這樣一個 0 -> 1 -> 2 , 3 -> 4 -> 1,如果只考慮結點0,那就需要擦除結點 3 與結點 4,在擦除結點的同時也需要更新入度。 - 判別迴環
如果在計算過程中碰到了一個鄰接點是已經算好的結點(入度為0),那必然存在環路 - 判別連通
只需要判別每一個終點是否都是初始值即可,值得一提的是本題不能加上這個判斷(不過可能是個人演算法的問題)
程式碼
#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;
}