刷题笔记-拓扑排序

1. 课程表

你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]。给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?
分析:如果用有向无环图表示课程的先修关系,可以将课程 0 指向课程 1,意为完成课程 0 的前提是完成课程 1。使用 dfs 解题。首先将课程的先修关系整理好,即课程 i 的先修课程都有哪些,课程 j 的先修课又有哪些,用 vector<vector<int>>存储这些关系。然后随便取一个课程作为入口,将其对应的 visited 数组设置为 1,意为该课程曾经访问过,再访问该课程的先修课,如果他们的 visited 数组是 1,证明他们曾经被访问过,也就是该有向图有环存在。如果他们的 visited 数组是 0,说明他们没被访问过,递归访问这些结点即可。如果当前课程以及他的先修课可以以某种顺序修完,那么将 visited 数组设置为2。

class Solution {
public:

    vector<vector<int>> Relation;
    vector<int> visited;
    bool valid = true;

    void dfs(int i)
    {
        visited[i]=1;
        for(auto j:Relation[i])
        {
            if(!visited[j])
            {
                dfs(j);
                if(valid == false)
                    return;
            }
            else if(visited[j]==1)
            {
                valid = false;
                return;
            }
        }
        visited[i]=2;
    }

    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        Relation.resize(numCourses);
        visited.resize(numCourses);

        for(auto edge:prerequisites)
            Relation[edge[1]].push_back(edge[0]);

        for(int i=0; i<numCourses && valid; ++i)
        {
            if(!visited[i])
                dfs(i);
        }
        return valid;
    }
};

2. 课程表II

现在你总共有 n 门课需要选,记为 0 到 n-1。在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]。给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/course-schedule-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

分析:此题与上一题相似,只是要记录下拓扑排序的结果。在每次完成一个课程的修习后(dfs函数返回前),都将当前课程号 push 到 vector 中即可。

class Solution {
public:

    vector<int> visited;
    vector<vector<int>> Relation;
    vector<int> ret;
    bool valid = true;
    int count = 0;

    void dfs(int i)
    {
        visited[i] = 1;
        for(const auto& j : Relation[i] )
        {
            if(!visited[j])
            {
                dfs(j);
                if(!valid) return;
            }
            else if(visited[j]==1)
            {
                valid = false;
                return;
            }
        }
        visited[i] = 2;
        ret[count] = i;
        ++count;
    }

    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        visited.resize(numCourses);
        Relation.resize(numCourses);
        ret.resize(numCourses);

        for(const auto& edge:prerequisites)
            Relation[edge[0]].push_back(edge[1]);

        for(int i=0; i<numCourses && valid; ++i)
        {
            if(!visited[i])
                dfs(i);
        }

        if(!valid){
            ret.clear();
        }
        return ret;
    }
};

3. 项目管理

公司共有 n 个项目和 m 个小组,每个项目要不没有归属,要不就由其中的一个小组负责。我们用 group[i] 代表第 i 个项目所属的小组,如果这个项目目前无人接手,那么 group[i] 就等于 -1。(项目和小组都是从零开始编号的)。请你帮忙按要求安排这些项目的进度,并返回排序后的项目列表:

(1) 同一小组的项目,排序后在列表中彼此相邻。
(2) 项目之间存在一定的依赖关系,我们用一个列表 beforeItems 来表示,其中 beforeItems[i] 表示在进行第 i 个项目前(位于第 i 个项目左侧)应该完成的所有项目。

结果要求:如果存在多个解决方案,只需要返回其中任意一个即可。如果没有合适的解决方案,就请返回一个 空列表。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sort-items-by-groups-respecting-dependencies
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

分析:该题目比较难的地方在于它要求同一小组的项目在排序后要相邻。所以可以根据项目间的依赖关系得出小组之间的依赖关系,从而对小组进行拓扑排序,对于没有小组认领的项目,由于可以拆分成很多个部分,出现在各小组之间,所以可以令他们独立成组。

class Solution {
public:
    vector<int> ret;//TopoItem
    vector<vector<int>> groupRelation;
    vector<vector<int>> group2Item;
    vector<int> cGroup;
    vector<int> gpVisited;
    vector<int> itemVisited;
    vector<int> TopoGroup;
    bool gpValid=true;
    bool itemValid=true;

    void dpsGroup(int i)
    {
        gpVisited[i]=1;
        for(const auto& j:groupRelation[i])
        {
            if(!gpVisited[j])
            {
                dpsGroup(j);
                if(!gpValid) return;
            }
            else if(gpVisited[j]==1)
            {
                gpValid = false;
                return;
            }
        }
        gpVisited[i]=2;
        TopoGroup.push_back(i);
    }

    void dpsItem(int i, vector<vector<int>>& ItemRelation)
    {
        itemVisited[i]=1;
        for(const auto& j:ItemRelation[i])
        {
            if(!itemVisited[j])
            {
                dpsItem(j,ItemRelation);
                if(!itemValid) return;
            }
            else if(itemVisited[j]==1)
            {
                itemValid=false;
                return;
            }
        }
        itemVisited[i]=2;
        ret.push_back(i);
    }

    vector<int> sortItems(int n, int m, vector<int>& group, vector<vector<int>>& beforeItems)     {
        //整理group,使组号为-1的独立成组
        cGroup.resize(n);
        cGroup=group;
        int count=0;
        for(auto& gpid:cGroup)
        {
            if(gpid==-1){
                gpid=m+count;
                ++count;
            }
        }

        //整理各组负责的项目
        group2Item.resize(m+count);
        for(int i=0; i<n; ++i)
            group2Item[cGroup[i]].push_back(i);

        //填充groupRelation,整理组之间的拓扑关系
        groupRelation.resize(m+count);
        for(int i=0; i<n; ++i)
        {
            for(auto j:beforeItems[i])
            {
                if(cGroup[i]!=cGroup[j])
                {
                    groupRelation[cGroup[i]].push_back(cGroup[j]);
                }
            }
        }

        //对组进行拓扑排序,排序结果放在TopoGroup中
        gpVisited.resize(m+count);
        for(int i=0; i<m+count && gpValid; ++i)
        {
            if(!gpVisited[i])
                dpsGroup(i);
        }
        if(!gpValid) return ret;

        //按组的拓扑排序对项目进行拓扑排序
        itemVisited.resize(n);
        for(int i=0; i<m+count; ++i)
        {
            for(const auto& item:group2Item[TopoGroup[i]])
            {
                if(!itemVisited[item]){
                    dpsItem(item, beforeItems);
                    if(!itemValid){
                        ret.clear();
                        return ret;
                    }
                }
            }
        }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值