圖論系列文章
前言
在學了前面三章之後,接下來學習圖論中最後的算法:拓撲排序與關鍵路徑算法
首先來了解一些基本概念。
AOV網與AOE網:
AOV(activity on vertex) 網:在執行後繼節點之前,必須將所有指向後繼節點的前驅節點都完成
AOE(activity on edge) 網:在執行後繼邊的活動之前,必須將前驅邊的活動都完成
AOV網與AOE網都類似於實際工程中的項目,必須將前驅的所有事件都完成之後,才能繼續後繼的事件。
接下來我們將使用下圖作爲圖的創建,提前將信息輸入到 graph.conf
中,再讓程序進行讀取。
- graph.conf
7
10
A B C D E F G
0 1 5
0 2 2
1 3 1
2 3 6
1 4 6
3 4 1
3 5 2
2 5 8
4 6 7
5 6 3
# 第一行 頂點個數
# 第二行 邊個數
# 第三行 頂點的元素
# 往下 邊連接的兩個頂點 以及 權
一.拓撲排序
拓撲排序是判斷有環無環的算法,也可以用來依次打印入度變爲0的拓撲序列。
算法核心
:
依次找到入度爲0的頂點,打印,並從圖中消去該頂點和弧,如果消去的頂點數==總頂點數,則無環;反之有環
在進行關鍵路徑的計算之前,必須先進行拓撲排序,判斷有環還是無環,只有無環才能夠計算關鍵路徑。
二.關鍵路徑
關鍵路徑是決定整個工程工期的路徑,意味着關鍵路徑中 活動的最早發生時間 == 活動的最晚發生時間 ,如果想要減小項目的工期與造價,那麼就需要從關鍵路徑入手。
算法核心
:
- ve:事件最早發生時間,最早也要等到前序所有的工作做完 (正向加 取權值最大)
- vl:事件最晚發生時間,最晚也要等到能保證完成後面的工作 (逆向見減 取權值最小)
- ae:活動最早發生事件,等於 頭頂點的的Ve值
- al:活動最晚發生時間,等於 尾頂點的Vl值減去權值
- 程序
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <stack>
#include <queue>
#include <climits>
#include <iomanip>
#include <algorithm>
#include <map>
#include <set>
using namespace std;
/*********************************圖*********************************************/
/*
AOV網與AOE網:
AOV(activity on vertex) 網:在執行後繼節點之前,必須將所有指向後繼節點的前驅節點都完成
AOE(activity on edge) 網:在執行後繼邊的活動之前,必須將前驅邊的活動都完成
拓撲排序:拓撲排序時判斷有環無環的算法
依次找到入度爲0的頂點,打印,並從圖中消去該頂點和弧,如果消去的頂點數==總頂點數,則無環;反之有環
關鍵路徑:
ve:事件最早發生時間,最早也要等到前序所有的工作做完 (正向加 取權值最大)
vl:事件最晚發生時間,最晚也要等到能保證完成後面的工作 (逆向見減 取權值最小)
ae:活動最早發生事件,等於 頭頂點的的Ve值
al:活動最晚發生時間,等於 尾頂點的Vl值減去權值
*/
using ElemType = char;
/*********************************類申明*********************************************/
struct EdgeNode
{
int beg;
int end;
int weight;
EdgeNode(int beg, int end, int weight) : beg(beg), end(end), weight(weight) {}
};
struct Comp
{
bool operator()(EdgeNode *lhs, EdgeNode *rhs)
{
return lhs->weight < rhs->weight;
}
};
class Graph
{
vector<vector<int>> _matrix; //鄰接矩陣
vector<ElemType> _vals; //存放頂點元素
vector<EdgeNode *> _edges; //存放所有邊的信息
map<int, set<EdgeNode *, Comp>> _vertexMap; //存放相應頂點的邊信息
vector<int> _inDegree; //存放每一個頂點的入度
int _vertexNum; //頂點數量
int _edgeNum; //邊的數量
vector<int> visited; //判斷定點是否訪問過
public:
Graph();
void printAdjMatrix(); //測試鄰接矩陣是否正確組合
void printvertexAndEdges(); //測試頂點及其相鄰邊是否正確
bool topologicalSort(); //進行拓撲排序,成功返回true, 失敗返回false(有環)
void criticalPath(); //找尋關鍵路徑
void resetVisited()
{
for (int i = 0; i < _vertexNum; i++)
visited[i] = 0;
}
private:
void createGraph();
};
/*********************************類定義*********************************************/
Graph::Graph()
{
createGraph();
visited.resize(_vertexNum);
}
void Graph::createGraph()
{
std::ifstream ifs("graph.conf", ios::in);
string str;
//第一行,讀取圖頂點的個數
{
getline(ifs, str);
stringstream ss(str);
ss >> _vertexNum;
_matrix.resize(_vertexNum);
for (int i = 0; i < _vertexNum; i++)
_matrix[i].resize(_vertexNum);
for (int i = 0; i < _vertexNum; i++)
for (int j = 0; j < _vertexNum; j++)
if (i == j)
_matrix[i][j] = 0;
else
_matrix[i][j] = INT_MAX;
}
//第二行,讀取邊的數量
{
getline(ifs, str);
stringstream ss(str);
ss >> _edgeNum;
}
//第三行,讀取頂點元素
{
getline(ifs, str);
stringstream ss(str);
ElemType val;
for (int i = 0; i < _vertexNum; i++)
{
ss >> val;
_vals.push_back(val);
}
}
//接下來,進行邊的連接
{
_inDegree.resize(_vertexNum);
for (int k = 0; k < _edgeNum; k++)
{
getline(ifs, str);
stringstream ss(str);
int i, j, weight; //用邊連接的兩個頂點的編號
ss >> i >> j >> weight;
_matrix[i][j] = weight;
//有向加權圖
EdgeNode *edge = new EdgeNode(i, j, weight);
_inDegree[j]++;
_edges.push_back(std::move(edge));
_vertexMap[i].insert(std::move(edge));
}
}
ifs.close();
}
void Graph::printAdjMatrix()
{
cout << "-----------------------------------" << endl;
for (int i = 0; i < _vertexNum; i++)
{
for (int j = 0; j < _vertexNum; j++)
{
if (INT_MAX == _matrix[i][j])
cout << " ∞ ";
else
cout << " " << _matrix[i][j] << " ";
}
cout << endl;
}
cout << "-----------------------------------" << endl;
}
void Graph::printvertexAndEdges()
{
for (int i = 0; i < _vertexNum; i++)
{
cout << _vals[i] << " : ";
set<EdgeNode *, Comp> edges = _vertexMap[i];
for (auto &e : edges)
{
cout << e->beg << "->" << e->end << " (" << e->weight << ") ";
}
cout << endl;
}
}
bool Graph::topologicalSort()
{
resetVisited();
stack<int> s;
int cnt = 0;
//找出入度爲0的頂點
for (int i = 0; i < _vertexNum; i++)
{
if (!visited[i] && 0 == _inDegree[i])
{
s.push(i);
visited[i] = 1;
cnt++;
}
}
cout << "拓撲排序:";
while (!s.empty())
{
//消去所有入度爲0的頂點的所有弧
while (!s.empty())
{
int v = s.top();
cout << _vals[v] << "->";
s.pop();
for (int j = 0; j < _vertexNum; j++)
{
if (_matrix[v][j] > 0 && _matrix[v][j] < INT_MAX)
_inDegree[j]--;
}
}
//找出入度爲0的頂點
for (int i = 0; i < _vertexNum; i++)
{
if (!visited[i] && 0 == _inDegree[i])
{
s.push(i);
visited[i] = 1;
cnt++;
}
}
}
cout << endl;
return (cnt == _vertexNum) ? true : false;
}
void Graph::criticalPath()
{
vector<int> ve(_vertexNum, 0);
vector<int> vl(_vertexNum, INT_MAX);
vector<int> ae(_edges.size());
vector<int> al(_edges.size());
vector<pair<int,int>> path;
//計算ve
for (int i = 0; i < _vertexNum; i++)
{
for (int j = 0; j < _vertexNum; j++)
{
if (_matrix[i][j] > 0 && _matrix[i][j] < INT_MAX)
if (ve[i] + _matrix[i][j] > ve[j])
ve[j] = ve[i] + _matrix[i][j];
}
}
//vl
vl[_vertexNum - 1] = ve[_vertexNum - 1];
for (int j = _vertexNum - 1; j >= 0; j--)
{
for (int i = 0; i < _vertexNum; i++)
{
if (_matrix[i][j] > 0 && _matrix[i][j] < INT_MAX)
if (vl[i] > vl[j] - _matrix[i][j])
vl[i] = vl[j] - _matrix[i][j];
}
}
//ae
int edgeSize = _edges.size();
int cnt = 0;
for (int i = 0; i < _vertexNum; i++)
for (int j = 0; j < _vertexNum; j++)
if (_matrix[i][j] > 0 && _matrix[i][j] < INT_MAX)
ae[cnt++] = ve[i];
//al
cnt = 0;
for (int i = 0; i < _vertexNum; i++)
for (int j = 0; j < _vertexNum; j++)
if (_matrix[i][j] > 0 && _matrix[i][j] < INT_MAX)
{
al[cnt++] = vl[j] - _matrix[i][j];
if (al[cnt-1] == ae[cnt-1])
path.push_back(std::make_pair(i,j));
}
//print
cout << "ve:" << endl;
for (int i = 0; i < _vertexNum; i++)
cout << ve[i] << " ";
cout << endl;
cout << "vl:" << endl;
for (int i = 0; i < _vertexNum; i++)
cout << vl[i] << " ";
cout << endl;
cout << "ae:" << endl;
for (int i = 0; i < edgeSize; i++)
cout << ae[i] << " ";
cout << endl;
cout << "al:" << endl;
for (int i = 0; i < edgeSize; i++)
cout << al[i] << " ";
cout << endl;
cout << "CriticalPath:" << endl;
for (int i = 0; i < path.size(); i++)
cout << _vals[path[i].first] << "->" << _vals[path[i].second] << " ";
cout << endl;
}
/*********************************測試函數*********************************************/
//TopologicalSort 判斷是否爲DAG並打印拓撲序列
void test0()
{
Graph graph;
graph.printAdjMatrix();
graph.printvertexAndEdges();
(graph.topologicalSort() == true) ? (cout << "無環" << endl) : (cout << "有環" << endl);
}
//Criticial_path
void test1()
{
Graph graph;
graph.printAdjMatrix();
graph.printvertexAndEdges();
graph.criticalPath();
}
int main()
{
test1();
return 0;
}