求点赞~(录了个视频讲解在b站)
有向 无环 图(DAG):
-一张没有坏的有向图,写作DAG
-常见做动态规划
动态规划:
-将状态抽象成一个点,向更新的点连边
-满足两点:①无后效性②最优子结构
无后效性:若u走到v,那么u一定在v之前出现,或者说f[u]在f[v]之前就计算完成了。 比如说很简单的一个例子,我用dp[i]记录i之前有多少个正数,那么dp[i]是不关心dp[i+1]的,也就是说在计算dp[i+1]之前dp[i]就已经计算好了,就满足了无后效性。
所以为了dp中的无后效性,我们必须需要一种对图的顺序,就是拓扑序,排序的方法就是拓扑排序。注意,拓扑序不唯一。
看个栗子:
那么对于这张图来说我的拓扑序就要求4在6前面,6在5前面,6在1前面,5在1前面,3在6前面,4在3前面,3在2前面,1在2前面,那么一个合法拓扑序就是 4-3-6-5-1-2
再举一个栗子:
可能的合法拓扑序:ABCGDFE, AGBCDFE, etc.
这时我们发现:
这些点入度为零的顺序就是一个拓扑序,也就有了拓扑排序算法。
算法复杂度:O(n+m)
算法步骤:
1. 找入度为零的点放入队列
2. 循环完成:
①取队首
②枚举连边
③删去连边(更新入度)
④新的入度为零的点放入队列
3. 出/入队顺序为拓扑序
如果有环:
例如这图会在第一次删边后就找不到入度为零的节点,那么出队的节点数量就不到n个
所以——拓扑序可以用来判别图中是否有环
上代码:
//in数组记录入度,g_e为链式前向星记录边,g_q为队列,r为记录拓扑序的数组
void topo_sort()
{
for(int i=1;i<=n;++i)
{
if(in[i]==0)
{
g_q.push(i);
}
}
while(!g_q.empty())
{
int tmp=g_q.front();
g_q.pop();
r[++tot]=tmp;
for(int i=head[tmp];i;i=g_e[i].nxt)
{
int v=g_e[i].v;
in[v]--;
if(in[v]==0)
{
g_q.push(v);
}
}
}
}
用一道例题:
说明
有向无环图上有n个点,m条边。求这张图字典序最小的拓扑排序的结果。字典序最小指希望排好序的结果中,比较靠前的数字尽可能小。
输入格式
第一行是用空格隔开的两个整数n和m,表示n个点和m条边。
接下来是m行,每行用空格隔开的两个数u和v,表示有一条从u到v的边。
1≤n,m≤100 000
注意:图上可能有重边
输出格式
输出一行,拓扑排序的结果,数字之间用空格隔开
样例
输入数据 1
5 3
1 2
2 4
4 3
输出数据 1
1 2 4 3 5
这道题让字典序最小,那么解决方案就是使用priority_queue小根堆,每次出队选择编号最小的就可以解决了。
上代码:
#include<bits/stdc++.h>
#define MAXN 200010
using namespace std;
int n,m,u,v,cnt=0,in[MAXN],r[MAXN],head[MAXN],tot=0;
priority_queue<int, vector<int>, greater<int> > g_q;
struct tEdge{
int u,v,nxt;
}g_e[MAXN];
void addedge(int u,int v)
{
g_e[++cnt].u=u,g_e[cnt].v=v;
g_e[cnt].nxt=head[u],head[u]=cnt;
}
void topo_sort()
{
for(int i=1;i<=n;++i)
{
if(in[i]==0)
{
g_q.push(i);
}
}
while(!g_q.empty())
{
int tmp=g_q.top();
g_q.pop();
r[++tot]=tmp;
for(int i=head[tmp];i;i=g_e[i].nxt)
{
int v=g_e[i].v;
in[v]--;
if(in[v]==0)
{
g_q.push(v);
}
}
}
for(int i=1;i<=tot;++i)
{
printf("%d ",r[i]);
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=m;++i)
{
scanf("%d %d",&u,&v);
addedge(u,v),addedge(v,u);
in[v]++;
}
topo_sort();
return 0;
}