拓撲排序的原理和實現


定義

在圖論中,由一個有向無環圖組成的序列,只要滿足下面兩種情況則稱為拓撲排序:

  • 每個頂點只允許訪問一次
  • 若頂點A在圖中存在到達頂點B的路徑,則不會存在頂點B到頂點A的路徑,也就是說這條路徑是單向的;

可以從這副圖中發現,如果按照DFS的思想,那么其訪問結點的結果為 5,2,3,1,0,4,但是如果是拓撲排序的話,訪問結點的結果為5,4,2,0,1,3,類似於多叉樹的BFS

問題

拓撲排序可用來解決什么問題呢?比如說課程排序,編譯依賴,類似凡是涉及到相關順序的時間安排;還可以用來判斷一幅有向圖是否無環。

思路

根據前面提供的思想,首先想到的就是BFS,但是需要在BFS的基礎上進行判斷,只有入度為0的結點才能加入到隊列中,其中每訪問一個結點,則將該結點的入度減一。(因為多叉樹的結點不可能存在環,所以其的BFS就不用擔心入度的問題)

如果是按照DFS的思想,則需要在等待迭代完結點的連接鄰接點后再把當前結點壓入棧中。

實現

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

/**
 * 構建鄰接矩陣
 */
class Graph {
public:
    Graph(int v);
    ~Graph();

    list<int>* adj; //鄰接表
    vector<int> nums; //統計結點的入度

    int V; //頂點數目

    // 添加一條從start為起始點,end為終點的邊
    void addEdge(int start, int end);
    void bfs_topological_sort();
    void dfs_topological_sort();

protected:
    void _bfs_topological_sort(vector<int> &result, queue<int>& queue);
    void _dfs_topological_sort(int v, bool visited[], stack<int>& stack);
};

Graph::Graph(int v) : V(v) {
    adj = new list<int>[V];
    nums.assign(v, 0);
}

Graph::~Graph() {

}

void Graph::addEdge(int start, int end) {
    this->adj[start].push_back(end);
    this->nums[end]++;
}

void Graph::_bfs_topological_sort(vector<int> &result, queue<int> &queue) {
    while (!queue.empty()) {
        int v = queue.front();
        queue.pop();

        result.push_back(v);
        for (auto itr = this->adj[v].begin(); itr != this->adj[v].end(); ++itr) {
            this->nums[*itr]--; //*itr結點的入度減1,也就是說刪掉 v 到 *itr 的有向邊
            if (!this->nums[*itr]) {
                queue.push(*itr);
            }
        }
    }

    // 判斷是否有環(正常情況下所有結點此時的入度都為0)
    for (int i = 0; i < V; i++) {
        if (this->nums[i] != 0) {
            cout << "have a ring" << endl;
        }
    }
}

void Graph::bfs_topological_sort() {
    queue<int> queue;

    // 計算所有的入度為0的結點,作為起點
    for (int i = V - 1; i >= 0; i--) {
        if (!this->nums[i]) {
            queue.push(i);
        }
    }

    vector<int> result;
    this->_bfs_topological_sort(result, queue);

    for (auto itr = result.begin(); itr != result.end(); itr++) {
        cout << *itr << "->";
    }
}

void Graph::_dfs_topological_sort(int v, bool *visited, stack<int> &stack) {
    if (visited[v]) return ;

    visited[v] = true;
    for (auto itr = this->adj[v].begin(); itr != this->adj[v].end(); ++itr) {
        if (!visited[*itr]) {
            _dfs_topological_sort(*itr, visited, stack);
        }
        else {
            // 判斷是否有環,不用可以刪掉(用鄰接矩陣表示更簡單,臨街表還是需要遍歷才能知道是否兩點之間是否連接)
            for (auto vitr = this->adj[*itr].begin(); vitr != this->adj[*itr].end(); ++vitr) {
                if (*vitr == v) {
                    cout << "ring is " << *itr << "->" << v << endl;
                }
            }
        }
    }

    // 訪問完結點所有的臨街結點之后才加入到棧中
    stack.push(v);
}

void Graph::dfs_topological_sort() {
    stack<int> stack;
    bool visited[V];
    memset(visited, false, sizeof(visited));

    for (int i = V - 1; i >= 0; i--) {
        if (!visited[i]) {
            _dfs_topological_sort(i, visited, stack);
        }
    }

    while (!stack.empty()) {
        int v = stack.top();
        stack.pop();
        cout << v << "->";
    }
};

int main() {

    // 構建鄰接矩陣
    Graph g(6); //6個結點
    g.addEdge(5, 2);
    g.addEdge(5, 0);
    g.addEdge(4, 0);
    g.addEdge(4, 1);
    g.addEdge(2, 3);
    g.addEdge(3, 1);
    g.addEdge(1, 3);

    g.bfs_topological_sort();
    g.dfs_topological_sort();

    return 0;
}

上面圖的表現形式為鄰接表,基本算法是用 BFSDFS 來實現的;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM