有向无环图
定义
没有环的有向图,也称为DAG
一个有向无环图
性质
性质一
从任意一点进行遍历,必不会陷入死循环
性质二
入度为0的点信息确定,新的入度为0的点信息也确定
拓扑序
写法
1.将入度为0的数入列;
2.将出队的节点的出边所指向的点的入度-1,如果该节点的入度也为0,那么将该节点入队;
3.当所有的点都出队,那么DAG的遍历结束
这样的遍历顺序被称为拓扑序
例题
题面
洛谷P1807https://www.luogu.com.cn/problem/P1807
示例代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
const int M=3e6+5;
int n,m;
int h[N],v[M],w[M],nx[M],tot,du[N],dis[N];
void add(int x,int y,int z){
tot++;
v[tot]=y;//存出边指向的点
w[tot]=z;//存边的长度
nx[tot]=h[x];//指向下一组数据
h[x]=tot;
}
long long read() {
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
return x*f;
}//快读
void topsort(){
queue<int>q;//建立队列
for(int i=1;i<=n;i++){
dis[i]=-2e9;//赋初值为-2e9
if(du[i]==0) q.push(i);//如果入度为0那么入对
}
dis[1]=0;//1到1的距离设为0
while (!q.empty()){
int x=q.front();//获得队列头
q.pop();//队列头出队
for(int i=h[x];i;i=nx[i]){
du[v[i]]-=1;//出边所指向的点入度-1
if(dis[x]!=-2e9) dis[v[i]]=max(dis[v[i]],dis[x]+w[i]);
//若出队的点不为初始值,证明与1相通,那么更新该点到1的距离
if(du[v[i]]==0 ) q.push(v[i]);
//如果入度为0则加入队列
}
}
}
int main(){
n=read();
m=read();
for(int i=1;i<=m;i++){
int x,y,z;
x=read();
y=read();
z=read();
add(x,y,z);//邻接表存储
du[y]+=1;//出边所指向的点入读加1
}
topsort();
if(dis[n]<=-1e9){
cout<<-1;
return 0;
}
// 无法抵达则不会被更新
cout<<dis[n];//输出1到n的距离
return 0;
}
拓扑排序
在DAG中,用拓扑序进行遍历,将结点的先后顺序输出就是拓扑排序;
这张DAG有两种拓扑排序结果:
2->5->1->3->4
5->2->1->3->4
写法
我们只需要把出队后的点进行存储,再输出后就可以记录拓扑排序
例题
题面
输入
3 3
1 2
2 3
1 3
输出
1 2 3
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
int h[N],v[N],nx[N],tot;
int dis[N],du[N];
priority_queue<int,vector<int>,greater<int> >q;
//优先队列(小根堆,也就是把队列内的数字从小往大排),因为是求最小字典序
vector<int>ans;//动态数组存储拓扑排序
void add(int x,int y){
tot++;
v[tot]=y;//存出边指向的点
nx[tot]=h[x];//指向下一组数据
h[x]=tot;
}
void bfs(){
for(int i=1;i<=n;i++){
dis[i]=-2e9;//赋初值为-2e9
if(du[i]==0) q.push(i);//如果入度为0那么入对
}
while(!q.empty()){
int x=q.top();//获得队列头
q.pop();//队列头出队
ans.push_back(x); //将节点放入拓扑排序的内容中
for(int i=h[x];i;i=nx[i]){
du[v[i]]--;//将出边指向的点的入度-1
if(du[v[i]]==0) q.push(v[i]);//如果该点的入度为0则入队
}
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
add(x,y);//邻接表存储
du[y]++;//出边指向的点入度加1
}
bfs();
if(ans.size()!=n){
cout<<-1;
return 0;
}//如果拓扑排序访问过的数量小于或大于总点数,那么则没有拓扑排序
for(int i=0;i<ans.size();i++) cout<<ans[i]<<' ';
//输出最小字典序的拓扑排序
return 0;
}
拓扑序解决层次问题
把节点分成若干层次,可以描述某一节点事件执行的优先级。
层次 | 节点编号 |
---|---|
1 | 1 |
2 | 2,3 |
3 | 4,5,6 |