【算法】BFS系列之 拓扑排序

【ps】本篇有 3 道 leetcode OJ。 

目录

一、算法简介

二、相关例题

1)课程表

.1- 题目解析

.2- 代码编写

2)课程表 II

.1- 题目解析

.2- 代码编写

3)火星词典

.1- 题目解析

.2- 代码编写


一、算法简介

【补】图的基本概念

(1)图是由顶点集合及顶点间的关系组成的一种数据结构:G = (V, E),其中:

  • 顶点集合V = {x|x属于某个数据对象集}是有穷非空集合;
  • E = {(x,y)|x,y属于V}或者E = {<x, y>|x,y属于V && Path(x, y)}是顶点间关系的有穷集合,也叫做边的集合。
  • (x, y)表示x到y的一条双向通路,即(x, y)是无方向的;Path(x, y)表示从x到y的一条单向通路,即Path(x, y)是有方向的。
  • 顶点和边:图中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边,图中的第k条边记作ek,ek = (vi,vj)或<vi,vj>。
  • 有向图和无向图:在有向图中,顶点对<x, y>是有序的,顶点对<x,y>称为顶点x到顶点y的一条边(弧),<x, y>和<y, x>是两条不同的边,比如下图G3和G4为有向图。在无向图中,顶点对(x, y)是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)和(y,x)是同一条边,比如下图G1和G2为无向图。注意:无向边(x, y)等于有向边<x, y>和<y, x>。

(2)入度和出度

        图中的度:所谓顶点的度(degree),就是指和该顶点相关联的边数。在有向图中,度又分为入度和出度。

  • 入度 (in-degree) :以某顶点为弧头,终止于该顶点的边的数目称为该顶点的入度。
  • 出度 (out-degree) :以某顶点为弧尾,起始于该顶点的弧的数目称为该顶点的出度。

(3)邻接表

        邻接表存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。

  • 在有向图中,描述每个点向别的节点连的边(“点 a->点 b”这种情况)。
  • 在无向图中,描述每个点所有的边(“点 a-点 b”这种情况)

(4)有向图邻接表存储

          拓扑排序简单来说就是找到做事情的先后顺序(但拓扑排序的结果可能不是唯一的)。 

        一个较大的工程往往被划分成许多子工程,我们把这些子工程称作活动(activity)。在整个工程中,有些子工程(活动)必须在其它有关子工程完成之后才能开始,也就是说,一个子工程的开始是以它的所有前序子工程的结束为先决条件的,但有些子工程没有先决条件,可以安排在任何时间开始。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称 AOV 网。

        在 AOV 网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列(Topological order),由 AOV 网构造拓扑序列的过程叫做拓扑排序(Topological sort)。AOV 网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。

        对于有向图的拓扑排序,我们可以使用如下思路输出拓扑序(BFS 方式):

  1. 起始时,将所有入度为 0 的节点进行入队(入度为 0,说明没有边指向这些节点,将它们放到拓扑排序的首部,不会违反拓扑序定义)。
  2. 从队列中进行节点出队操作,出队序列就是对应我们输出的拓扑序。对于当前弹出的节点  x,遍历 x 的所有出度y,即遍历所有由 x 直接指向的节点 y,对 y 做入度减一操作(因为 x 节点已经从队列中弹出,被添加到拓扑序中,等价于 x 节点从有向图中被移除,相应的由 x 发出的边也应当被删除,带来的影响是与 x 相连的节点 y 的入度减一)。
  3. 对 y 进行入度减一之后,检查 y 的入度是否为 0,如果为 0 则将y入队(当y的入度为0,说明有向图中在y前面的所有的节点均被添加到拓扑序中,此时 可以作为拓扑序的某个片段的首部被添加,而不是违反拓扑序的定义)。
  4. 循环流程 2、3 直到队列为空。

        至于如何建图,请见下文例题。

二、相关例题

1)课程表

207. 课程表

.1- 题目解析

        不难看出,这些课程可以构成一个有向无环图,本题其实在问,这些课程构成的有向无环图中是否有环,换句话说,是否可以进行拓扑排序。

        那么,用 BFS 来实现拓扑排序即可。

.2- 代码编写

class Solution {
public:
    bool canFinish(int n, vector<vector<int>>& prerequisites) {
        unordered_map<int,vector<int>> edges; //用容器(邻接表)建图
        vector<int> in(n); //标识每一个点的入度
        //1.遍历原始数组建图
        for(auto& e:prerequisites)
        {
            int a=e[0],b=e[1]; //b -> a
            edges[b].push_back(a); //把b指向a的这条边添加入邻接表
            in[a]++; //统计入度
        }
        //2.BFS实现拓扑排序
        queue<int> q;
        //1)把所有入度为0的点入队
        for(int i=0;i<n;i++)
        {
            if(in[i]==0)q.push(i);
        }
        //2)进行BFS
        while(q.size())
        {
            int t=q.front();q.pop();
            for(int a: edges[t]) //遍历t连接的点
            {
                in[a]--; //修改点的入度,相当于删除点
                if(in[a]==0)q.push(a); //继续将入度为0的点入队
            }
        }
        //3,判断是否有环
        for(int i=0;i<n;i++)
            if(in[i])return false;//拓扑排序之后,每个点的入度都应为0

        return true;
    }


};

2)课程表 II

210. 课程表 II

.1- 题目解析

        本题与上道题类似,也是用 BFS 实现拓扑排序,但与上道题判断是否有环不同,本题返回的是拓扑排序的一种结果。

.2- 代码编写

class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<vector<int>> edges(numCourses);//用容器(邻接表)存图
        vector<int> in(numCourses);           //统计入度
        //1.建图
        for(auto& v:prerequisites)
        {
            int a=v[0],b=v[1]; // b -> a
            edges[b].push_back(a);
            in[a]++;
        }
        //2.BFS实现拓扑排序
        queue<int> q;
        vector<int> ret;
        for(int i=0;i<numCourses;i++)
        {
            if(in[i]==0)q.push(i);
        }
        while(q.size())
        {
            int t=q.front();q.pop();
            ret.push_back(t);//记录拓扑排序的结果
            for(int a:edges[t])
            {
                in[a]--;
                if(in[a]==0)q.push(a);
            }
        }

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

3)火星词典

LCR 114. 火星词典

.1- 题目解析

        本题是在问,判断有向图是否有环,因此可以用 BFS 实现拓扑排序来解决。

        特别的,如何搜集信息(如何建图)?——

  1. 两层 for 循环枚举出所有的两个字符串的组合。
  2. 利用指针,根据字典序规则找出信息。

.2- 代码编写

class Solution {
    unordered_map<char,unordered_set<char>> edges;//用容器(邻接表)存图
    unordered_map<char,int> in;                   //统计入度
    bool check;                                   //处理边界情况
public:
    string alienOrder(vector<string>& words) {
        //1.建图+初始化入度哈希表
        for(auto& s:words)
            for(auto ch:s)
                in[ch]=0;
        //2.搜集信息
        int n=words.size();
        for(int i=0;i<n;i++)
            for(int j=i+1;j<n;j++)
            {
                add(words[i],words[j]);//添加信息
                if(check)//处理边界情况
                    return "";
            }
        //3.拓扑排序
        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);
            }
        }
        //4.判断是否有环
        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],b=s2[i]; //a -> b
                if(!edges.count(a) || !edges[a].count(b)) //a没有存过,或a存过但a里没存过b的信息
                {
                    edges[a].insert(b);
                    in[b]++;
                }
                break;
            }
        }
        if(i==s2.size() && i<s1.size())//s1的长度大于s2,就无需进行拓扑排序了
            check=true;
    }
};

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值