拓扑排序的原理和实现

定义

在图论中,由一个有向无环图组成的序列,只要满足下面两种情况则称为拓扑排序:

  • 每个顶点只允许访问一次
  • 若顶点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 来实现的;

posted @ 2017-04-06 13:42  banananana  阅读(7665)  评论(1编辑  收藏  举报