【Lintcode】127. Topological Sorting

题目地址:

https://www.lintcode.com/problem/topological-sorting/description

对一个有向图进行拓扑排序,返回任意一个符合条件的排序。

法1:BFS,即Kahn’s Algorithm。具体做法是,先求出所有顶点的入度,然后取所有入度等于 0 0 0的顶点加入拓扑排序的结果里,接着进行BFS,将这些入度为 0 0 0的顶点的前驱邻边依次删掉,一旦某个顶点的入度删为了 0 0 0,就将其加入拓扑排序的结果里,并将其入队。如此重复操作下去即可。代码如下:

import java.util.*;

public class Solution {
    /*
     * @param graph: A list of Directed graph node
     * @return: Any topological order for the given graph.
     */
    public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) {
        // write your code here
        // 存储拓扑序答案
        ArrayList<DirectedGraphNode> res = new ArrayList<>();
        // 存储所有顶点的入度
        Map<DirectedGraphNode, Integer> indegrees = getIndegree(graph);
        
        // 为了BFS开一个队列
        Queue<DirectedGraphNode> queue = new LinkedList<>();
        
        // 先将所有入度为0的点入队,显然拓扑排序就以这些顶点开始
        for (DirectedGraphNode node : graph) {
            if (indegrees.get(node) == 0) {
                queue.offer(node);
                res.add(node);
            }
        }
        
        while (!queue.isEmpty()) {
        	// 从队列中取出一个顶点,并删去所有从这个顶点到其邻居节点的邻边
        	// 一旦发现某个顶点的前驱邻边全删完了,也就是入度变为0了,就加入拓扑序的结果,并将其入队
            DirectedGraphNode node = queue.poll();
            for (DirectedGraphNode neighbor : node.neighbors) {
                indegrees.put(neighbor, indegrees.get(neighbor) - 1);
                if (indegrees.get(neighbor) == 0) {
                    queue.offer(neighbor);
                    res.add(neighbor);
                }
            }
        }
        
        // 如果拓扑序的结果的大小等于图的顶点个数,说明所有的顶点都已经排序好了
        // 说明此图是可以拓扑排序的,返回res;否则返回null
        return res.size() == graph.size() ? res : null;
    }
    
    // 统计所有顶点的入度
    private Map<DirectedGraphNode, Integer> getIndegree(ArrayList<DirectedGraphNode> graph) {
    	// 初始化一个map,存储所有顶点的入度
        Map<DirectedGraphNode, Integer> indegrees = new HashMap<>();
        
        // 先将所有顶点的入度初始化为0
        for (DirectedGraphNode node : graph) {
            indegrees.put(node, 0);
        }
        
        // 接着遍历所有图中的点,将其邻居的入度 + 1
        for (DirectedGraphNode node : graph) {
            for (DirectedGraphNode neighbor : node.neighbors) {
                indegrees.put(neighbor, indegrees.get(neighbor) + 1);
            }
        }
        
        return indegrees;
    }
}

class DirectedGraphNode {
    int label;
    ArrayList<DirectedGraphNode> neighbors;
    DirectedGraphNode(int x) {
        label = x;
        neighbors = new ArrayList<>();
    }
}

时间复杂度 O ( V + E ) O(V+E) O(V+E),空间 O ( V ) O(V) O(V)

算法正确性证明:
统计所有顶点的入度的方法显然是正确的。先将整个图分成若干个连通块,然后对每个连通块进行考虑。以上算法的做法是,先将入度为 0 0 0的点加入队列和拓扑排序的结果中,接着开始遍历这些顶点,然后对于其后继节点,不停删除其前驱邻边。如果某个顶点的前驱邻边被删干净了,那么上述算法会将其加入最终结果中。这一步显然是正确的,因为其前驱节点一定已经在结果集中了,不违反拓扑排序的定义。接着将其入队。由数学归纳法可以得到,每次加入结果集中的顶点,其前驱节点必然已经存在在结果集里了(当然除了入度为 0 0 0的点,但这不妨碍正确性),并且,所有与入度为 0 0 0的点连通的点,也都加入结果集了。最后如果结果集的大小等于图的顶点个数的话,显然拓扑排序完成。否则的话,说明存在一些点,从入度为 0 0 0的点们出发到这些点的邻边全删掉之后,这些点的入度仍然不为 0 0 0,那么就一定存在环,说明此图不可拓扑排序。证明完毕。

法2:DFS。DFS生成树在递归返回的时候天然生成一个拓扑序的逆序。所以可以用后序遍历的方式,在DFS到某个顶点的时候,将其邻接点全遍历完后,再将其加入一个列表中。代码如下:

import java.util.*;

public class Solution {
    /*
     * @param graph: A list of Directed graph node
     * @return: Any topological order for the given graph.
     */
    public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) {
        // write your code here
        ArrayList<DirectedGraphNode> res = new ArrayList<>();
        
        // 记录已经访问过的顶点的label
        Set<Integer> visited = new HashSet<>();
		
		// 开始对图中每个未访问的顶点进行DFS
        for (DirectedGraphNode node : graph) {
            if (!visited.contains(node.label)) {
                dfs(node, res, visited);
            }
        }
        
    	// 此时res是拓扑序的逆序,需要反一下
        Collections.reverse(res);
        return res;
    }
    
    private void dfs(DirectedGraphNode cur, List<DirectedGraphNode> res, Set<Integer> visited) {
    	// 标记当前顶点为已经访问过
        visited.add(cur.label);
        for (DirectedGraphNode neighbor : cur.neighbors) {
            if (!visited.contains(neighbor.label)) {
                dfs(neighbor, res, visited);
            }
        }
        
        // 递归返回的时候,意味着cur的子孙节点们都被访问过了,此时将cur加入res,形成一个拓扑序的逆序
        res.add(cur);
    } 
}

时空复杂度 O ( V ) O(V) O(V)

证明可以考虑任意的两个顶点,由于从 u u u递归返回到 v v v意味着在拓扑序中 v v v u u u之前,而在算法中DFS结束后,在res中 u u u恰好在 v v v之前,所以最后反一下的时候就是个拓扑序。所以算法正确。

注解:
DFS求拓扑序不需要求入度,代码很短,比较方便。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值