第十五章 图的BFS与拓扑序列

一、图的BFS

1、思路

在这里插入图片描述
上图中的遍历顺序就是以A为起点开始的广度优先搜索。先遍历距离A点最近的距离,然后再依次向外拓展。

2、模板

(1)问题

在这里插入图片描述
题目当中提到了最短路,同时每条边的权重都是1,同时在边权为1的情况下,我们的广度优先搜索是具备最短路的性质的。因此,我们采用BFS去做这道题。而最短路的证明,在前面讲解DFS和BFS的时候证明过,大家可以自行去看,这里附上链接:

同时,在该文章中,我还为大家介绍了,为什么BFS要用队列,如何保证搜到的是最短的等等常见问题:

DFS与BFS保姆级教学

(2)代码模板

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e5+10;
const int M=3e5+10;
int h[N],e[M],ne[M],idx;
int m[N];
int n,k;
void add(int x,int y)
{
    e[idx]=y;
    ne[idx]=h[x];
    h[x]=idx++;
}
int bfs()
{
    queue<int>q;
    q.push(1);
    m[1]=0;
    while(!q.empty())
    {
        for(int i=h[q.front()];i!=-1;i=ne[i])
        {
            if(m[e[i]]==-1)
            {
                m[e[i]]=m[q.front()]+1;
                q.push(e[i]);
            }
            if(e[i]==n)goto A;
        }
        q.pop();
    }
    A:
    return m[n];
}
int main()
{
    memset(h,-1,sizeof h);
    memset(m,-1,sizeof m);
    cin>>n>>k;
    for(int i=0;i<k;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    cout<<bfs();
    return 0;
}

(3)代码解析

在这里插入图片描述

二、拓扑序列

引入:

在生活中,我们经常会遇到类似一下的场景。
在这里插入图片描述
我们想要去上学的话,我们必须完成前面的一系列任务。不起床,怎么吃饭呢?不穿衣服,怎么上学呢?(当然,正常人的话)
那么我们有一下两条路可走:

起床–》穿鞋–》穿衣服–》吃饭–》背书包–》上学
起床–》穿衣服–》穿鞋–》吃饭–》背书包–》上学

这两条路都可以走,所以这两条路线都是正确的,这就是拓扑序列

1、什么是拓扑序列?

在这里插入图片描述
简单的说,拓扑序列就是有顺序地去访问图中的点。按照我们刚才的例子,我们发现,当一个点没有被指向的时候,这个点就相当于没有限制的,那么我们就可以直接访问,类似于A和E。那么被指向的路线个数称作:入度。从该点指出的路线个数叫做:出度

比如:A点的入度为0,出度为1。D点的入度为2,出度为3。

所以,我们只有当某个点的入度是0的时候,才能够访问。

那么上图中,我们可以直接访问A和E,但是先访问谁都可以,因此拓扑序列并不唯一。例如上图中,我们可以写出一条可能的拓扑序列:

在这里插入图片描述

什么情况下没有拓扑序列呢?
我们看下面的情况;
在这里插入图片描述
当一个图中出现这种环的时候,我们是无法写出拓扑序列的,因为图中没有度为0的点,因此我们无从下手。

2、模板:

(1)问题:

在这里插入图片描述
很明显,我们想要写出一个拓扑序列,就要从一个入度为0的点开始,当我们访问结束后,就可以删除这个点以及和这个点相关的边,然后再找下一个入度为0的点。即:逐个击破!

部分过程如下图所示:
在这里插入图片描述
那么我们的思路就是先找到所有入度为0的点,然后通过BFS的逻辑,扫描该入度为零的点周围相连的点。为什么这样做呢?我们的目的就是通过BFS的扫描去删除该点所连的边。就如同上图中的D点。
我们通过D点,去扫描离他最近的CFG点,然后删除DC边,DF边,DG边。当删除以后,我们发现,C点从入度为1变成了入度为0,也就是说这个点可以访问了,那么我们让这个点进队。最后,我们发现这个队中的数据就是拓扑序列。具体的例子可以看图中左侧的红色字母序列。

(2)代码模板:

#include<iostream>
#include<cstring>
using namespace std;
const int N=1e5+10,M=3*N;
int h[N],e[M],ne[M],q[N],d[N],idx;
int n,m;
void add(int x,int y)
{
    e[idx]=y,ne[idx]=h[x],h[x]=idx++;
}
bool topsort()
{
    int hh=0,tt=-1;
    for(int i=1;i<=n;i++)if(!d[i])q[++tt]=i;
    while(hh<=tt)
    {
        int front=q[hh++];
        for(int i=h[front];i!=-1;i=ne[i])
        {
            if(--d[e[i]]==0)q[++tt]=e[i];
        }
    }
    int size=tt+1;
    return size==n;
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d %d",&n,&m);
    for(int i=0;i<m;i++)
    {
        int x,y;
        scanf("%d %d",&x,&y);
        add(x,y),d[y]++;
    }
    if(!topsort())puts("-1");
    else
    {
        for(int i=0;i<n;i++)printf("%d ",q[i]);
    }
    
}

(3)模板分析:

在这里插入图片描述

(4)注意:

STL中的队列行不行?

这里不能使用STL中的队列,因为STL中的队列,会把队头真的删掉,但是我们知道,队列中的数据存储的是我们的答案,我们只能通过模拟队列的方式,伪删头部,即通过指针的偏移来删除。这样做的话,我们的答案是会被保留下来的。

为什么这里的BFS不用标记?

我们这里只是采用了BFS的思想,但不是真正的BFS,所以我们会发现如果一个点的入度大于1的话,那么这个点是会被重复遍历的。所以,并不是BFS。我们只是通过一个入度为0的点,去扫描与他相连的最近的点,从而达到删除边的效果,类似于BFS。

如何判断是否成功?

如果一个图是拓扑排序的话,那么我们能利用逐个击破的思路访问到每一个点,也就是说我们的每一个点都会入队。我们的尾部指针指向的是当前的尾部元素的下标。但是我们的头是从下标为0的点开始的。所以我们的元素个数等于尾部指针+1。比如,尾指针指向1,但是我们的元素有q[0],q[1],此时我们的个数是2。

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值