BFS 解决拓扑排序 , 课程表 , 课程表 II , 火星词典

拓扑排序简介

1.有向无环图(DAG图)

像这样只能从一个点到另一个点有方向的图,并且不构成环状就是有向无环图
在这里插入图片描述
如果像这样,4,5,6就构成环状了,就不是有向无环图了。

在这里插入图片描述

出度是指一个顶点作为起点出发的边的数量,而入度是指指向该顶点的边的数量

在这里插入图片描述

2.AOV网:顶点活动图

在有向无环图中,用顶点来表示一个活动,用边来表示活动的先后顺序的图结构。

eg:

在这里插入图片描述

3.拓扑排序

找到做事情的先后顺序,拓扑排序的结果可能不是唯一的。
重要应用:判断有向图中是否有环

用上面那个炒菜的例子:

1.先把买菜拿出来(买菜没有被任何箭头指向,准备厨具也可以)

在这里插入图片描述

  1. 准备厨具和洗菜都可以拿出来

在这里插入图片描述

  1. 只能选择洗菜

在这里插入图片描述
5. 可以选择腌肉或者切菜(剩下依次类推)

在这里插入图片描述

如何排序

  1. 找出图中入度为0的点,然后输出
  2. 删除与该点连接的边
  3. 重复1,2操作,直到图中没有点或者没有入度为0的点(有可能有环)为止

4.实现拓扑排序

借助队列,来一次bfs即可

  1. 初始化,把所有入度为0的点加入队列中
  2. 当队列不为空的时候:
    a. 拿出队头元素,加入最终结果中
    b. 删除与该元素相连接的边;
    c. 判断:与删除边相连接的点,是否入度变为0(如果入度为0,就加入队列)

207. 课程表

在这里插入图片描述

由题目可的建网类似这样:
在这里插入图片描述

实际上这道题就是在问:

能否拓扑排序
是否是有向无环图 - 有向图中是否有环?

如何建图 ? 灵活的使用容器

更倾向于unordered_map<int, vector> edgs; 建表,就像0 -> 1,2,3 ,好用。
在这里插入图片描述

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        unordered_map<int,vector<int>> edges;//邻接表存图
        vector<int> in(numCourses); //标记每一个定义的入度

        //建图
        for(auto& e : prerequisites)
        {
            int a = e[0], b =e[1]; //b -> a
            edges[b].push_back(a);
            in[a]++;
        }

        //拓扑排序  
        queue<int> q;
        //把所有入度为0的点的加入队列
        for(int i = 0; i<numCourses; i++)
        {
            if(in[i] == 0)
                q.push(i);
        }

        //bfs
        while(q.size())
        {
            int t = q.front();
            q.pop();
            
            for(auto a : edges[t])
            {
                in[a]--;
                if(in[a] == 0)
                    q.push(a);
            }
        }

        //判断是否有环
        for(int i = 0; i<numCourses; i++)
        {
            if(in[i])
                return false;
        }

        return true;
    }
};

210. 课程表 II

在这里插入图片描述

本质上跟上一道题一样,只不过这个让返回课程的学习顺序

我们只需要在每一次取出队列中最上面的时候,把它存入一个vector数组中即可,最后判断数组中的数和课程总数是否相等就可以。

class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        unordered_map<int,vector<int>> edgs;//邻接表存图
        vector<int> vis(numCourses);//标记每一个定义的入度

        //建表
        for(auto& e : prerequisites)
        {
            int a = e[0],b = e[1]; //b->a
            edgs[b].push_back(a);
            vis[a]++;
        }

        //拓扑排序
        queue<int> q;
        vector<int> ret;
        //把所有入度为0的点放入队列
        for(int i = 0; i<numCourses; i++)
        {
            if(vis[i] == 0)
                q.push(i);
        }

        while(q.size())
        {
            int t = q.front();
            ret.push_back(t);
            q.pop();

            for(auto a : edgs[t])
            {
                vis[a]--;
                if(vis[a] == 0)
                    q.push(a);
            }
        }

        if(ret.size() == numCourses)
            return ret;
        else return {}; 
    }
};

LCR 114. 火星词典

在这里插入图片描述

如何收集信息

用两层for循环来搜集信息

在这里插入图片描述
一遍一遍循环比较来建有向无环图

在这里插入图片描述

细节问题

像abc 和 ab 这种比较并不合法
在这里插入图片描述

class Solution {
public:
    unordered_map<char,unordered_set<char>> edges;//邻接表存图
    unordered_map<char,int> in; //统计入度
    bool valid; //处理边界情况

    string alienOrder(vector<string>& words) {
        int n = words.size();

        //建表+初始化入度哈希表
        for(auto& s : words)
        {
            for(auto ch : s)
            {
                in[ch] = 0;
            }
        }
        for(int i = 0; i<n; i++)
        {
            for(int j = i+1; j<n; j++)
            {
                add(words[i],words[j]);
                if(valid) return "";
            }
        }

        //拓扑排序
        queue<char> q;
        for(auto& [a,b] : in)
        {
            if(b == 0) q.push(a); 
        }    

        string ret;
        while(q.size())
        {
            char t =q.front();
            q.pop();
            ret+=t;
            for(char ch : edges[t])
            {
                if(--in[ch] == 0)
                    q.push(ch);
            }
        }
        
        //判断
        for(auto &[a,b] : in)
        {
            if(b != 0)
                return "";
        }

        return ret;
    }


    void add(string& s1,string& s2)
    {
        int n = min(s1.size(),s2.size());
        int i = 0;
        for(;i<n;i++)
        {
            if(s1[i] != s2[i])
            {
                char a = s1[i];
                char b = s2[i];
                // a -> b
                if(!edges.count(a) || !edges[a].count(b))
                {
                    edges[a].insert(b);
                    in[b]++;
                }
                break;
            }
        }

        if(i == s2.size() && i < s1.size()) //判断不合法的情况
            valid = true;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值