圖論(四)拓撲排序與關鍵路徑


圖論系列文章

圖論(一) 鄰接表與鄰接矩陣

圖論(二) 最短路徑:Dijkstra算法與Floyd算法

圖論(三)最小生成樹:Prim算法與Kruskal算法

圖論(四)拓撲排序與關鍵路徑


前言

在學了前面三章之後,接下來學習圖論中最後的算法:拓撲排序與關鍵路徑算法
首先來了解一些基本概念。
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章