有向无环图的拓扑序列
一下都是个人学习拓扑序列的一些理解,并非绝对正确的描述
拓扑序列都是针对有向无环图的而言的,是对有向无环图的顶点进行线性排序,使得每个顶点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 << ' ';
}
❀完结撒花❀