拓扑排序

14 篇文章 0 订阅
11 篇文章 0 订阅

拓扑排序

定义:

对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

注意:

只有当一幅有向图是无环图时才能进行拓扑排序。

应用:

任务调度、课程安排、继承

207. Course Schedule

题目描述:

共有n门课程你必须参加,标签从0到n-1。

有些课程可能有先决条件,例如,要参加课程0,您必须首先参加课程1,这表示为一对:[0,1]

考虑到课程总数和先决条件对列表,您是否可以完成所有课程?可以返回true,不可以返回false

Input: 2, [[1,0]] 
Output: true

Input: 2, [[1,0],[0,1]]
Output: false
解决:

这道不需要使用拓扑排序,只需要检测有向图是否存在环即可,在这里采用DFS,递归来实现

/**
*采用深度优先遍历解决有无环
*如果采用BFS会更快
**/
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<Integer> [] graphic = new List[numCourses];
        boolean [] marked = new boolean[numCourses];		//判断那个数是否被标记
        for (int i = 0;i < numCourses;i++){
            graphic[i] = new ArrayList();		//对每个数字生成一个链表
        }
        for (int [] pre : prerequisites){
            graphic[pre[0]].add(pre[1]);
        }
        for (int i = 0;i < numCourses;i++){
            if(hasCycle(graphic,marked,i)){
                return false;
            }
        }
        return true;
    }
    //判断是否含环
    private boolean hasCycle(List<Integer> [] graphic,boolean [] marked,int cur){
        if (marked[cur]){
            return true;		//如果被标记,则直接返回true
        }
        marked[cur] = true;
        for (int next : graphic[cur]){
            if (hasCycle(graphic,marked,next)){
                return true;
            }
        }
        marked[cur] = false;
        return false;
    }
}

210. Course Schedule II

题目描述:

总共有n门课程你必须参加,标签从0到n-1。

有些课程可能有先决条件,例如,要参加课程0,您必须首先参加课程1,这表示为一对:[0,1]

给定课程总数和先决条件对列表,返回完成所有课程所需的课程顺序。

可能有多个正确的订单,您只需返回其中一个。如果无法完成所有课程,则返回空数组。

Input: 2, [[1,0]] 
Output: [0,1] 

Input: 4, [[1,0],[2,0],[3,1],[3,2]]
Output: [0,1,2,3] or [0,2,1,3]
解题:

这道属于典型拓扑排序,可以考虑用DFS或者BFS来完成,在LeetCode上显示第一种这种解法比第二种更快点。

DFS:使用一个栈存储后序遍历结果,这个栈的逆序结果就是拓扑排序结果,这里有一个globalMarked全局判断,可以减少判断次数。

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        List<Integer> [] graphic = new List[numCourses];
        for (int i = 0;i < numCourses;i++){
            graphic[i] = new ArrayList<>();
        }
        for(int [] a : prerequisites){
            graphic[a[0]].add(a[1]);
        }
        boolean [] globalMarked = new boolean[numCourses];	//增加一个全局判断,可以减少时间
        boolean [] localMarked = new boolean[numCourses];
        Stack<Integer> postOrder = new Stack<>();	//使用栈存储后序遍历结果
        for(int i = 0;i < numCourses;i++){
            if(hasCycle(globalMarked,localMarked,postOrder,graphic,i)){
                return new int[0];
            }
        }
        int [] order = new int[numCourses];
        for(int i = numCourses - 1;i >= 0;i--){
            order[i] = postOrder.pop();	//逆序输出
        }
        return order;
    }
    //DFS,判断是否有环
    private boolean hasCycle(boolean [] globalMarked,boolean [] localMarked,Stack<Integer> postOrder,
                             List<Integer>[] graphic,int i ){
        if(localMarked[i]){
            return true;
        }
        if(globalMarked[i]){
            return false;	//减少判断
        }
        localMarked[i] = true;
        globalMarked[i] = true;
        for(int next : graphic[i]){
            if(hasCycle(globalMarked,localMarked,postOrder,graphic,next)){
                return true;
            }
        }
        localMarked[i] = false;
        postOrder.add(i);
        return false;
    }
}

BFS:首先遍历图中所有的顶点,将入度为0的顶点入队列,然后将入度为0的顶点出队列,并更新与之邻接的顶点的入度,若邻接顶点的入度降为0,则入队列,最后判断index与numCourses得大小,如果有环,index的值 一定小numCourses。

public int[] findOrder(int numCourses, int[][] prerequisites) { 
    if (numCourses == 0) return null;
    // 将图形表示从边转换为相邻列表
    int indegree[] = new int[numCourses], order[] = new int[numCourses], index = 0;
   //判断哪些有先决条件得课程
    for (int i = 0; i < prerequisites.length; i++) 
        indegree[prerequisites[i][0]]++;    

    Queue<Integer> queue = new LinkedList<Integer>();
    for (int i = 0; i < numCourses; i++) 
        if (indegree[i] == 0) {
            // 将课程添加到订单中,因为它没有先决条件,即将入度为0的顶点入队列
            order[index++] = i;
            queue.offer(i);
        }

    while (!queue.isEmpty()) {
        int prerequisite = queue.poll(); 
        //更新与之邻接的顶点的入度
        for (int i = 0; i < prerequisites.length; i++)  {
            if (prerequisites[i][1] == prerequisite) {
                indegree[prerequisites[i][0]]--; 
                if (indegree[prerequisites[i][0]] == 0) {
                    //如果Indegree为零,则将课程添加到order中
                    order[index++] = prerequisites[i][0];
                    queue.offer(prerequisites[i][0]);
                }
            } 
        }
    }
    
	//如果有环,index的值 一定小于numCourses
    return (index == numCourses) ? order : new int[0];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值