【算法训练】拓扑排序

280 阅读2分钟

看到依赖问题,首先想到把问题转化成 有向图 这种数据结构,如果图中有环,证明存在依赖循环

这里记录,用图,和拓扑排序的思想来解决的两道题目。用DFS和BFS都可以做,但是BFS代码更为简洁,这里写了BFS代码

一、课程表 207

在这里插入图片描述

1、分析

由于课程与课程之间存在依赖问题,我们将其转化为图。用图去记录课程与课程之间的关系,而且我们选择邻接表的形式记录。且将被依赖项作为起始点指向目标项。如a依赖b,我们在建图的时候是b指向a。 记录每个节点的入度,如果入度为0,证明不依赖任何项。我们首先将入度为0的节点加入队列初始化。然后开始执行BFS循环,从队列中弹出一个节点,减少相邻节点的入度,同时新产生入度为0的节点加入队列中。重复上述过程,如果能够遍历到所有节点,证明没有环的存在。否则有环。

具体见代码。

2、代码

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        graph,incount = {},{}
        for i in range(numCourses):
            graph[i] = []
            incount[i] = 0
        for t,f in prerequisites:
            graph[f] += [t]
            incount[t] += 1
        print(graph)
        que = collections.deque()
        for i in range(numCourses):
            if incount[i]==0:
                que.append(i)
        count = 0
        while que:
            temp = que.popleft()
            count += 1
            for next in graph[temp]:
                incount[next] -= 1
                if incount[next]==0:
                    que.append(next)
        return count==numCourses

JS

/**
 * @param {number} numCourses
 * @param {number[][]} prerequisites
 * @return {boolean}
 */
var canFinish = function(numCourses, prerequisites) {
    let graph={},incount={};
    for(let i=0;i<numCourses;i++){
        graph[i] = [];
        incount[i] = 0;
    }
    for(item of prerequisites){
        let t=item[0],f=item[1];
        graph[f] = [...graph[f],t];
        incount[t] ++;
    }
    let count=0,que=[];
    for(let i=0;i<numCourses;i++){
        if(incount[i]===0){
            que.push(i);
        }
    }
    while(que.length!=0){
        let temp = que.shift();
        count ++;
        for(let next of graph[temp]){
            incount[next] --;
            if(incount[next]==0){
                que.push(next);
            }
        }
    }
    return count===numCourses;

};

二、课程表II 210

在这里插入图片描述

1、分析

这道题与上一道题的区别在于要记录遍历的过程,其实这个遍历就是一个拓扑排序的结果。具体见代码和注释。

2、代码

class Solution:
    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
        # 用res记录最终结果
        res = []
        # graph记录生成图
        graph = {}
        # incount用于记录每个节点的入度
        incount = {}
        # 初始化邻接表和入度记录表
        for i in range(numCourses):
            graph[i] = []
            incount[i] = 0
        # 遍历依赖关系,生成图,以及统计节点入度
        for t,f in prerequisites:
            graph[f] += [t]
            incount[t] += 1
        # 利用双端队列记录初始化队列中的节点
        que = collections.deque()
        # 入度为零表示没有其他依赖,可以作为拓扑排序的起点
        for i in range(numCourses):
            if incount[i]==0:
                que.append(i)
        # 记录遍历节点的个数
        count = 0
        while que:
            temp = que.popleft()
            count += 1
            # 弹出节点的顺序就是拓扑排序的结果
            res.append(temp) 
            for next in graph[temp]:
                incount[next] -= 1
                if incount[next] == 0:
                    que.append(next)
        # 如果存在环,拓扑排序将不存在
        if count!=numCourses:
            return []
        return res

JS

/**
 * @param {number} numCourses
 * @param {number[][]} prerequisites
 * @return {number[]}
 */
var findOrder = function(numCourses, prerequisites) {
    let res = [],graph={},incount ={};
    for(let i=0;i<numCourses;i++){
        graph[i] = [];
        incount[i] = 0;
    }
    for(let item of prerequisites){
        let f=item[1],t=item[0];
        graph[f] = [...graph[f],t];
        incount[t] ++;
    }
    let count=0,que=[];
    for(let i=0;i<numCourses;i++){
        if(incount[i]===0){
            que.push(i);
        }
    }
    while(que.length!=0){
        let temp = que.shift();
        count ++;
        res.push(temp);
        for(let next of graph[temp]){
            incount[next] --;
            if(incount[next]===0){
                que.push(next);
            }
        }
    }
    if(count!=numCourses){
        return [];
    }
    return res;

};