图论之拓扑排序 ( 有向无环图的拓扑序列 与 无向有环图的拓扑序列)

有向无环图的拓扑序列

一下都是个人学习拓扑序列的一些理解,并非绝对正确的描述
拓扑序列都是针对有向无环图的而言的,是对有向无环图的顶点进行线性排序,使得每个顶点u到顶点v的每条有向边uv,u在序列中都在v之前。举个栗子:4399游戏里的森林冰火人应该都玩过吧,它的关卡就是一个拓扑序列,你必须打通了第一关,才能解锁第二层的那些关卡。
拓扑序列图解
即每个顶点只出现一次,并且每条有向边的起点都在终点之前。

拓扑序的求法

(入度就是该点被几条边指向,出度就是有几条边以该点为起点)
首先我们需要找到入度为0的点(入度为0确定这个点一定为起点),将以它为起点的边删除,输出该点,然后继续重复找点删边的操作直到最后一个点。
拿上图举例:首先找到1这个点,把1->2和1->3删除,2和3的入度就为0了(如果入度都为0,我们习惯从点数小的开始遍历),然后找到2这个点,将2->4删除,发现4的入度不为0,然后找到3这个点,将3->4删除,此时点4的入度为0,最后找到4这个点,发现没有需要删除的边,结束程序。

一般而言我都是采用邻接表存储拓扑图,然后用数组模拟队列实现找点,删边。
说空的概念没什么效率,直接上题。

有向图的拓扑序列(Acwing848)

AC代码如下:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5+10;

int h[N],e[N],ne[N],idx;//邻接表
int d[N];//用来存储入度
int q[N];//数组模拟队列
int n,m;

bool bfs()
{
	//用数组模拟队列
    int hh = 0,tt = -1;
    for(int i = 1;i<=n;i++)
    if(!d[i])q[++tt] = i;
    while(hh<=tt)
    {
        auto t = q[hh++];
        for(int i = h[t];i!=-1;i = ne[i])//遍历邻接表
        if(--d[e[i]] == 0)q[++tt] = e[i];//每删一条边,终点的入度就减一
    }
    return tt == n-1;//tt是从0开始,存n个点,tt最终会等于n-1
}

int main()
{
    cin >> n >> m;
    //以下为邻接表存储有向图
    memset(h,-1,sizeof(h));
    for(int i = 1,a,b;i<=m;i++)
    {
        cin >> a >> b;
        d[b]++;
        ne[++idx] = h[a];
        e[idx] = b;
        h[a] = idx;
    }
    if(bfs())
    {
        int hh = 0;
        while(hh<=n-1)cout << q[hh++] << ' ';
    }
    else cout << -1;
}

有向无环图的拓扑序列都很正常,但是蓝桥杯里有“发现环”这个题目=-=、

无向有环图的拓扑序列

发现环

题目描述是有n台电脑,每两台电脑之间有一条无向边(双向边),构成了一棵树,但是现在多连了一条无向边,使树中有环,我们需要找出这个环中所有的顶点并且以升序输出。
按照拓扑序列的特点与写题的习惯,我们都是惯性思维找入度为0的点,但是这题不可能有入度为0的点,所以我们换个思路,先思考无向无环图,应该怎么找拓扑序列(相对而言拓扑序列,1<->2<->3<->4这个无向无环图,应当输出1 2 3 4(我规定的=-=+)) 当然是找入度为1的点啦,只有入度为1才可能是边界点,所以现在我们再思考无向有环图的拓扑序列,在一个环中,所有点的入度都为2,找不到入度为1的点,而不在环中的点入度可能为1,所以我们只需要使用找拓扑序列的方法,把所有入度为1的点一个个找出来,最后就只剩下一个环(即再剩下的图中找不到入度为1的点,剩下的环中点入度均为2),所以我们从小到大输出入度为2的点就结束啦~
AC代码如下:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5+10;
vector<int> g[N];//存无向图,这里不用邻接表存无向图的原因是有点麻烦=-=
//剩下的和有向无环图的拓扑序列一致
int n;
int d[N];
int q[N];

void bfs()
{
  int hh = 0;int tt = -1;
  for(int i = 1;i<=n;i++)
  if(d[i]==1)q[++tt] = i;//这里与有向无环图的拓扑序列不同,我们需要找入度为1的点

  while(hh<=tt)
  {
    auto t = q[hh++];
    for(auto v:g[t])
    if(--d[v] == 1)q[++tt] = v;
  }
}

int main()
{
  cin >> n;
  存无向图
  for(int i = 1,a,b;i<=n;i++)
  {
      cin >> a >> b;
      d[a]++;
      d[b]++;
      g[a].push_back(b);
      g[b].push_back(a);
  }
  bfs();
  //输出所有入度为2的点(一定要有这个if语句!!!!
  for(int i = 1;i<=n;i++)if(d[i]==2)cout << i << ' ';
}

❀完结撒花❀

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值