LintCode-127: Topological Soring 拓扑排序 (经典题)

在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英语:Topological sorting)。

每个顶点出现且只出现一次;
若A在序列中排在B的前面,则在图中不存在从B到A的路径。
也可以定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面。

Topological Sorting可以用BFS和DFS实现。

算法流程
拓扑排序的算法是典型的宽度优先搜索算法,其大致流程如下:

  1. 统计所有点的入度,并初始化拓扑序列为空。
  2. 将所有入度为 0 的点,也就是那些没有任何依赖的点,放到宽度优先搜索的队列中
  3. 将队列中的点一个一个的释放出来,放到拓扑序列中,每次释放出某个点 A 的时候,就访问 A 的相邻点(所有A指向的点),并把这些点的入度减去 1。
  4. 如果发现某个点的入度被减去 1 之后变成了 0,则放入队列中。
  5. 直到队列为空时,算法结束,

深度优先搜索的拓扑排序
深度优先搜索也可以做拓扑排序,不过因为不容易理解,也并不推荐作为拓扑排序的主流算法。

  1. Topological Sorting

Given an directed graph, a topological order of the graph nodes is defined as follow:

For each directed edge A -> B in graph, A must before B in the order list.
The first node in the order can be any node in the graph with no nodes direct to it.
Find any topological order for the given graph.

Example
For graph as follow:

picture

The topological order can be:

[0, 1, 2, 3, 4, 5]
[0, 2, 3, 1, 5, 4]

Challenge
Can you do it in both BFS and DFS?

Clarification
Learn more about representation of graphs

Notice
You can assume that there is at least one topological order in the graph.

解法1:BFS宽搜
基于queue。先做预处理,把统计所有节点的in-degree,用一个hashmap存起来。然后把in-degree为0的节点放入queue中(同时也放入结果中),然后进入while循环,每次把queue中的front元素取出来,遍历其邻居,把每个邻居的in-degree减一。若该邻居的in-degree降到0,则将其放入queue中(同时也放入结果中),如此往复,直到queue为空 。
注意这个模板与二叉树分层遍历或骑士遍历稍有区别。因为后面两个都是分层遍历,所以内部的for循环是基于queue size。而这里不是分层遍历,所以内部的for循环是基于queue front的neighbors。
代码如下:

/**
 * Definition for Directed graph.
 * struct DirectedGraphNode {
 *     int label;
 *     vector<DirectedGraphNode *> neighbors;
 *     DirectedGraphNode(int x) : label(x) {};
 * };
 */
vector<DirectedGraphNode*> topSort(vector<DirectedGraphNode*>& graph) {
        vector<DirectedGraphNode*> result;
        map<DirectedGraphNode*, int> hashmap;
        queue<DirectedGraphNode*> q;
        for (int i=0; i<graph.size(); ++i) {
            for (int j=0; j<graph[i]->neighbors.size(); ++j) {
                //if (hashmap.find(graph[i]->neighbors[j])==hashmap.end()) {
                //    hashmap[graph[i]->neighbors[j]]=1;
                //} else {
                    hashmap[graph[i]->neighbors[j]]++;
                //}
            }
        }
    
        for (int i=0; i<graph.size(); ++i) {
            if (hashmap[graph[i]]==0) {
                q.push(graph[i]);
                result.push_back(graph[i]);
            }
        }
        
        while(!q.empty()) {
            DirectedGraphNode * node = q.front();
            q.pop();
            for (auto nb : node->neighbors) {
                hashmap[nb]--;
                if (hashmap[nb]==0) {
                    result.push_back(nb);                    
                    q.push(nb);
                }
            }
        }   
        return result;
    }

注意:

  1. 若不存在topological-sorting(也即存在环),结果会如何?会不会在while中陷入死循环呢?答案是不会陷入死循环,队列中存的是入度为0的点,我们将这些点拿出来,假设拿出m个,如果有环,那么肯定有点的入度不为0,最后只需要判断m是否等于n即可,n表示图中点的个数。
  2. while()前的那个for循环不可以用下面的代码代替,因为如果in-degree=0,那么这个节点根本不会在mp中。
        for (auto m : mp) {
            if (m.second == 0) {
                q.push(m.first);
                result.push_back(m.first);
            }
        }

二刷:

/**
 * Definition for Directed graph.
 * struct DirectedGraphNode {
 *     int label;
 *     vector<DirectedGraphNode *> neighbors;
 *     DirectedGraphNode(int x) : label(x) {};
 * };
 */

class Solution {
public:
    /**
     * @param graph: A list of Directed graph node
     * @return: Any topological order for the given graph.
     */
    vector<DirectedGraphNode*> topSort(vector<DirectedGraphNode*> graph) {
        int graphSize = graph.size();
        if (graphSize == 0) return {};
        
        unordered_map<DirectedGraphNode*, int> indegrees;
        
        for (int i = 0; i < graphSize; i++) {
            for (auto neighbor : graph[i]->neighbors) {
                indegrees[neighbor]++;
            }  
        }

        queue<DirectedGraphNode *> q;
        for (int i = 0; i < graphSize; i++) {
            if (indegrees[graph[i]] == 0) {
                //res.push_back(graph[i]);
                q.push(graph[i]);
            }
        }

        vector<DirectedGraphNode*> res;
        while (!q.empty()) {
            DirectedGraphNode* frontNode = q.front();
            q.pop();
            res.push_back(frontNode);
            for (auto neighbor : frontNode->neighbors) {
                indegrees[neighbor]--;
                if (indegrees[neighbor] == 0) {
                    q.push(neighbor);
                }
            }
         }
        
        return res;
    }
};

解法2:DFS
下次 再写。

代码同步在:
https://github.com/luqian2017/Algorithm


注意:拓扑排序的结果就是后序遍历的反转。下面这个帖子解释得很好。
https://www.zhihu.com/question/28549004/answer/41237756
“DAG的拓扑顺序的定义其实很简单,即按拓扑顺序记录(register)DAG的节点,任一个节点必须在它的所有父节点都被记录后才可以被记录。那么看一下后序遍历(即先子后父)的不变量(Invariant)是什么:  任何一个子节点的位置皆先于其所有父节点。那么取上述后序遍历的逆序必有不变量:  任何一个子节点必位于其所有父节点之后。即符合拓扑顺序的定义。反之,观察先序遍历(先父后子)的不变量:  任何一个节点都跟随其第一个父节点被记录。  即任一节点仅需第一个父节点被记录后,就必然先于其第二个父节点被记录,从而不能导出其“晚于所有父节点被记录”的拓扑顺序,即充分性不成立。同样可以证明,任一拓扑顺序要求先(所有)父后子,当且仅当每子仅一父时先序遍历才满足之(此时DAG成为一棵树),即必要性不成立。综上,在DAG中,逆后序和拓扑顺序是充分必要关系,而先序和拓扑顺序是非充分非必要关系。“

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值