基本概念
-
人们有时要做很多事情,这些事情之间又有依赖关系或者说有优先级之分,只有优先级高的事情做完之后才能去做优先级低的事,比如穿衣服的先后,课程学习的顺序,安排客人的座位等。
-
例如:存在事情a,b,c,d,其中a的优先级最高,b,c的优先级其次,d的优先级最低,则abcd或者acbd就是两种排序,我们把事情变成点,先后关系变成边,则在图中求一个可行的排序,就是拓扑排序
-
一个图能进行拓扑排序的充要条件是它是一个有向无环图(DAG)
算法学习
图的入度与出度
- 出度:以点u为起点的边的数量
- 入度:以点v为终点的边的数量
- 一个点的入度和出度的数量间接体现了这个点的优先级,当一个点不存在入度的时候,则该点就是优先级最高的点
BFS拓扑排序(模板)
- 原理:无前驱的顶点优先拓扑排序
- 方法:每次将入度为0的点加入队列,作为起点,弹出该点,去遍历该点的所有邻居点,将邻居点的入度减1,如果该邻居点的入度减少到0,则将其加入队列,以此类推
- 判断拓扑排序无解的方法:当队列空的时候仍存在点还未进入过队列,则这些点的入度都不为0,则说明图不是DAG,不存在拓扑排序
void bfs(){
for(int i=1;i<=n;i++)if(!in[i])q.push(i);
while(!q.empty()){
int u=q.top();
cout<<u<<" ";
for(int i=vex[u];i;i=e[i].next){
int v=e[i].v;
in[v]--;
if(!in[v])q.push(v);
}
q.pop();
}
}
int main(){
int m,u,v;
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>u>>v;
add(u,v);
in[v]++;
}
bfs();
return 0;
}
字典序最小的拓扑排序
- 即,若存在若干个优先级相同的点,优先输出点编号较小的点。只需要优先队列(小根堆)代替普通队列即可。
priority_queue<int,vector<int>,greater<int> >q;
void bfs(){
for(int i=1;i<=n;i++)if(!in[i])q.push(i);
while(!q.empty()){
int u=q.top();
cout<<u<<" ";
q.pop();
for(int i=vex[u];i;i=e[i].next){
int v=e[i].v;
in[v]--;
if(!in[v])q.push(v);
}
}
}
int main(){
int m,u,v;
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>u>>v;
add(u,v);
in[v]++;
}
bfs();
return 0;
}
小值尽可能先做的拓扑排序
- 小值尽可能先做等价于大值尽可能后做,反向建图跑字典序大的拓扑序即可得到逆序的答案。
priority_queue<int>q;
void bfs(){
for(int i=1;i<=n;i++)if(!in[i])q.push(i);
while(!q.empty()){
int u=q.top();
ans[++top]=u;
q.pop();
for(int i=vex[u];i;i=e[i].next){
int v=e[i].v;
in[v]--;
if(!in[v])q.push(v);
}
}
for(int i=top;i>=1;i--)cout<<ans[i]<<" ";
}
int main(){
int m,u,v;
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>u>>v;
add(u,v);
in[u]++;
}
bfs();
return 0;
}