定義
在圖論中,由一個有向無環圖組成的序列,只要滿足下面兩種情況則稱為拓撲排序:
- 每個頂點只允許訪問一次;
- 若頂點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;
}
上面圖的表現形式為鄰接表,基本算法是用 BFS
和 DFS
來實現的;