【定义】
对一个有向无环图(Directed Acyclic Graph,简称DAG)G 进行拓扑排序,是将 G 中所有顶点排成一个线性序列,使得图中任意一对顶点 u 和 v,若边 (u,v)∈E(G),则 u 在线性序列中出现在 v 之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。
我们可以用一个比较形象的例子——选课,来理解这个东西。假设我非常想学习一门 A 课程,但是在修这个课程之前,我们必须要学习一些基础课程,比如 B,C,D 等等。那么这个制定选修课程顺序的过程,实际上就是一个拓扑排序的过程,每门课程相当于有向图中的一个顶点,而课程学习的先后关系就是连接顶点之间的有向边。
【Kahn算法】
Kahn算法的过程如下:
- 从图中选择一个入度为 0 的节点并输出
- 删除这个节点并且删除与这个节点相连的边
- 重复进行前两步,直到所有点都输出了,或者找不到入度为 0 的点了(有环)
我们用栈来维护一个点集,里面存储入度为 0 的节点
#include<stack>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define M 200005
using namespace std;
int n,m,t;
stack<int>sta;
int v[M],next[M];
int du[N],first[N];
void add(int x,int y)
{
t++;
next[t]=first[x];
first[x]=t;
v[t]=y;
}
int main()
{
int x,y,i,num=0;
scanf("%d%d",&n,&m); //n是点数,m是边数
for(i=1;i<=m;++i)
{
scanf("%d%d",&x,&y); //从x到y的单向边
du[y]++; //y的入度增加1
add(x,y); //用前向星来存这张图
}
for(i=1;i<=n;++i)
if(!du[i])
sta.push(i); //将入度为0的点入栈
while(!sta.empty())
{
x=sta.top(); //取出栈顶元素
sta.pop();
printf("%d ",x); //输出栈顶元素
for(i=first[x];i;i=next[i]) //将所有与x相连的边删掉
{
du[v[i]]--; //v[i]的入度减1
if(!du[v[i]])
sta.push(v[i]); //如果v[i]的入度为零,就将v[i]入栈
}
}
return 0;
}
【时间复杂度】
Kahn算法的时间复杂度为O(n+m),即点数+边数,嗯,非常优秀
【例题】
这道题其实也是一道DP题
用 f [ i ] 表示以 i 为终点最多能浏览的城市数量
由于只能往东走,考虑拓扑排序,删边的时候更新就可以了
#include<stack>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define M 200005
using namespace std;
int n,m,t;
stack<int>sta;
int v[M],next[M];
int f[N],du[N],first[N];
void add(int x,int y)
{
t++;
next[t]=first[x];
first[x]=t;
v[t]=y;
}
int main()
{
int x,y,i,num=0;
scanf("%d%d",&n,&m);
for(i=1;i<=m;++i)
{
scanf("%d%d",&x,&y);
du[y]++;
add(x,y);
}
for(i=1;i<=n;++i)
if(!du[i])
f[i]=1,sta.push(i);
while(!sta.empty())
{
x=sta.top();
sta.pop();
for(i=first[x];i;i=next[i])
{
f[v[i]]=max(f[v[i]],f[x]+1);
du[v[i]]--;
if(!du[v[i]])
sta.push(v[i]);
}
}
for(i=1;i<=n;++i)
printf("%d\n",f[i]);
return 0;
}