首先说明一下关于图中顶点的度

无向图中顶点的度,指和该顶点相关联的边数。

有向图中顶点的度分为入度和出度,通俗的讲入度就是箭头指向自己(顶点)的边的个数,出度就是箭头背离自己的边的个数。

拓扑排序详解及其习题_i++

有向图

b的入度为3 出度为0,c的入度为1 出度为2,d的入度为2 出度为1,a的入度为1 出度为4

拓扑排序详解及其习题_#include_02

无向图

v1点的度为4,v3点的度为2,v1点的度数为4(一个环有两个度)

------->直入主题

拓扑排序:有向图顶点的线性排序就是其拓扑排序。其实可以理解为一个有依赖关系的任务顺序。前提是有向无环图。
例如,图形的顶点可以表示要执行的任务,并且边可以表示一个任务必须在另一个任务之前执行的约束; 在这个应用中,拓扑排序只是一个有效的任务顺序。当且仅当图形没有定向循环,即如果它是有向无环图(DAG),则拓扑排序是可能的。
                                                                                                                                                                    ——来自维基百科

 

在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英语:Topological sorting)。
1、每个顶点出现且只出现一次;
2、若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

易于理解的解释:

比如有人想要制作一件工具,但是这个工具不是一次就可以完成的,分很多个步骤,而且这些步骤是有顺序的,也就是说,假设B的顺序在A的后面,那么你就必须要先完成A再完成B,但是也有些步骤不分顺序,意思是你先做哪一个都是可以的。

比如学习拓扑排序都会提到的这个很火的课程修读关系图:

拓扑排序详解及其习题_i++_03

每个顶点表示学期中的课程,箭头表示学习的先后顺序,有向边( A->D)表示在学习高等代数之前必须要学完代数。我们怎么才能将这些课程处理成一个序列呢?这个序列是我们学习课程,序列保证,箭头左边的点不会出现在箭头右边点的后面。拓扑排序就是处理这样的问题哦

求拓扑排序常用的方法:

(1)从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。
(2)从图中删除该顶点和所有以它为起点的有向边。
(3)重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环

拓扑排序详解及其习题_Max_04

 求此图的拓扑排序。

 

拓扑排序详解及其习题_#include_05

 

模板代码:

#include <bits/stdc++.h>
#define Max 505
using namespace std;
vector<int> vec[Max]; //vec 存图
priority_queue<int,vector<int>,greater<int> > q; //优先队列
int in_deg[Max];//入度
int ans[Max];
int n,m;
void topsort()
{
for(int i=1;i<=n;i++)
{
if(in_deg[i]==0)
{
q.push(i);
}
}
int cnt=0;
while(!q.empty())
{
int u=q.top();
q.pop();
ans[cnt++]=u;
for(int v=0;v<vec[u].size();v++)
{
in_deg[vec[u][v]]--;
if(!in_deg[vec[u][v]])
{
q.push(vec[u][v]);
}
}
}

if(cnt!=n) cout<<"-1"<<endl; //环
else {
cout<<ans[0];
for(int i=1;i<cnt;i++)
cout<<" "<<ans[i];
cout<<endl;
}
}

有关拓扑排序的简单题及代码:

​HDU-1285​

/*
拓扑模板 +优先队列
*/
#include <bits/stdc++.h>
#define Max 505
using namespace std;
vector<int> vec[Max]; //vec 存图
priority_queue<int,vector<int>,greater<int> > q; //优先队列
int in_deg[Max];//入度
int ans[Max];
int n,m;
void init()
{
memset(in_deg,0,sizeof(in_deg));
for(int i=0;i<=n;i++)
{
vec[i].clear();
}
while(!q.empty())
{
q.pop();
}
}
void topsort()
{
for(int i=1;i<=n;i++)
{
if(in_deg[i]==0)
{
q.push(i);
}
}
int cnt=0;
while(!q.empty())
{
int u=q.top();
q.pop();
ans[cnt++]=u;
for(int v=0;v<vec[u].size();v++)
{
in_deg[vec[u][v]]--;
if(!in_deg[vec[u][v]])
{
q.push(vec[u][v]);
}
}
}

if(cnt!=n) cout<<"-1"<<endl; //环
else {
cout<<ans[0];
for(int i=1;i<cnt;i++)
cout<<" "<<ans[i];
cout<<endl;
}
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>n>>m)
{
init();
int u,v;
for(int i=0;i<m;i++)
{
cin>>u>>v;
vec[u].push_back(v);
in_deg[v]++;
}
topsort();
}
return 0;
}

​HDU-3342​

/*
HH is 3xian's master and, at the same time, 3xian is HH's master,which is quite illegal!
To avoid this,please help us to judge whether their relationship is legal or not.
判断有没有环
*/
#include <bits/stdc++.h>
#define Max 101
using namespace std;
vector<int> vec[Max];
int in_deg[Max];
queue<int>q;
int n,m;
void init(int nn)
{
for(int i=0;i<=nn;i++)
{
vec[i].clear();
}
while(!q.empty()) q.pop();
memset(in_deg,0,sizeof(in_deg));
}
int topsort()
{
for(int i=0;i<n;i++)
{
if(in_deg[i]==0)
{
q.push(i);
}
}
int cnt=0;
while(!q.empty())
{
int u=q.front();
q.pop();
cnt++;
for(int v=0;v<vec[u].size();v++)
{
in_deg[vec[u][v]]--;
if(!in_deg[vec[u][v]])
{
q.push(vec[u][v]);
}
}
}
/*
if(cnt==n)
{
return 1;//有向无环图
}else return 0;//非 有向无环图
*/
return cnt==n;
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>n>>m)
{
if(n==0&&m==0) break;
init(n);
int u,v;
for(int i=0;i<m;i++){
cin>>u>>v;
vec[u].push_back(v);
in_deg[v]++;
}
bool ans=topsort();
if(ans==1) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}

​HDU-2647​

/*
题意:老板给员工发工资
n个员工,m中关系
每次输入 a,b
要求:a的工资必须比 b 高
最低工资是 888
求老板一共最少需要付多少工资
如果不能满足所有人的需求的话 就输出-1
*/
#include <bits/stdc++.h>
#define ll long long
#define Max 10002
using namespace std;
int n,m;
int mon[Max];//分层 相当于每个人888工资的加成
int in_deg[Max];
vector<int> vec[Max];
queue<int>q;
void init(int nn)//注意初始化
{
for(int i=0;i<=nn;i++)
{
vec[i].clear();
}
memset(in_deg,0,sizeof(in_deg));
while(!q.empty()) q.pop();
memset(mon,0,sizeof(mon));
}
int topsort()
{
for(int i=1;i<=n;i++)
{
if(in_deg[i]==0)
{
q.push(i);
}
}
int cnt=0;
while(!q.empty())
{
int u=q.front();
q.pop();
cnt++;
for(int v=0;v<vec[u].size();v++)
{
in_deg[vec[u][v]]--;
if(!in_deg[vec[u][v]])
{
q.push(vec[u][v]);
mon[vec[u][v]]=max(mon[u]+1,mon[vec[u][v]]);
//money分层
}
}
}
return cnt==n;
}
int main()
{
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
ios::sync_with_stdio(false);
while(cin>>n>>m)
{
init(n);
int u,v;
for(int i=0;i<m;i++)
{
cin>>u>>v;
vec[v].push_back(u);// 反向建边
in_deg[u]++;
}
ll ans=0;
if(topsort())
{

for(int i=1;i<=n;i++)
{
// cout<<i<<": "<<mon[i]<<endl;
ans+=(888+mon[i]);
}
cout<<ans<<endl;
}else
{
cout<<"-1"<<endl;
}
}return 0;
}
/*
4 4 1 2 2 3 1 4 3 4
3 2 1 2 1 3
3 1 1 2

7 7
1 2
2 6
3 2
3 4
4 5
5 6
7 6


*/

​POJ-1094​

/*
we will give you a set of relations of the form A < B and ask you to determine whether a sorted order has been specified or not.
1、处理到第一个关系的时候能全部确定所有关系 -> Sorted sequence determined after x relations: %&^%.
2、处理到第几个时能发现矛盾(环) -> Inconsistency found after x relations.
3、没有矛盾但是给出的关系无法确定关系 -> Sorted sequence cannot be determined.

题意就是给你一系列的关系,让你从他给出的关系中从小到大排一个序,如果给出的关系中发生冲突,输出发生冲突的那一步,如果可以得到一个序,输出题目给出到第几个关系才能确定顺序。
注意:之前已经确定了符合约束的序列,那么之后再给出的矛盾的条件是无效的
eg:--->
5 5
A<B
B<C
C<D
D<E
E<A
output:
Sorted sequence determined after 4 relations: ABCDE
----------------------------------------------------------------------
因为最多只有26个字母,所以怎么写都不会超时。
*/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
int n,m,cnt;
int in_deg[30];//节点入度数组
int in_degTemp[30];//临时节点入度数组 为了保证加入一个关系后 前后 节点度的统一
char Ans[30];//最后排序的结果
vector<int>vec[30]; //存图
queue<int>q; //队列
void init()
{
//26个英文字母
for(int i=0;i<26;i++)
{
vec[i].clear();
}
while(!q.empty()) q.pop();
memset(in_deg,0,sizeof(in_deg));
memset(in_degTemp,0,sizeof(in_degTemp));
memset(Ans,0,sizeof(Ans));

}
int topsort()
{
//每次输入一个关系要判断一次是否有结果,
//in_degTemp是 记录每一次加边后-度-改变的-数组 in_deg不能改
for(int i=0;i<n;i++)
{
if(!in_deg[i])
{
q.push(i);
}
}
bool unSure=false;//不确定(3)的情况的标记
cnt=0;
while(!q.empty())
{
//一旦出现有的点没有涉及(比较的关系中),入度为0的点必然大于1,则肯定可以说明 有的点无法确定关系
if(q.size()>1) unSure=true;
int u=q.front();
q.pop();
Ans[cnt++]=u+'A';
for(int v=0;v<vec[u].size();v++)
{
in_deg[vec[u][v]]--;
if(!in_deg[vec[u][v]])
{
q.push(vec[u][v]);
}
}
}
if(cnt<n)//存在环
{
return 3;
}
if(unSure)//不确定
{
return 2;
}
return 1;// 确定了
}
int main()
{

while(scanf("%d%d",&n,&m)!=EOF)
{
init();
if(n==0&&m==0) break;
char op[3];
bool flag=false;
int step=0,ans;
for(int i=0;i<m;i++)
{
scanf("%s",op);
//已经有结果了 就不需要判断后边的关系 但是 还要读入所有数据
if(flag) continue;
vec[op[0]-'A'].push_back(op[2]-'A');
in_deg[op[2]-'A']++;
memcpy(in_degTemp,in_deg,sizeof(in_deg));
ans=topsort();//每加一个关系 拓扑处理一次 并且用临时数组存一下,当前入度情况
memcpy(in_deg,in_degTemp,sizeof(in_degTemp));
if(ans!=2){
step=i+1;
flag=true;
}//如果不是无结果的话 那么就是 回路或者有结果
}
if(ans==1){
Ans[cnt]='\0';
printf("Sorted sequence determined after %d relations: %s.\n",step,Ans);
}else if(ans==2)
{
printf("Sorted sequence cannot be determined.\n");
}else if(ans==3)
{
printf("Inconsistency found after %d relations.\n", step);
}
}
return 0;
}

​HDU-4857​

/*
注意此题不是保证字典序,而是要最小的尽量在前面。
对于有m组限制即a必须在b前面出去,而对于那些没有限制的就必须按照标号从小到大出去,也就是说假设有三个人,3号必须在1号前面除去,2号没限制,那么出去的顺序为3 1 2,为了满足这个需求,反向建图+拓扑排序+优先队列+逆序输出
*/
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <queue>
#define ll long long
#define Max 30002
using namespace std;
int n,m,a,b;
int in_deg[Max],ans[Max];
vector<int> vec[Max];
priority_queue<int,vector<int>,less<int> > q;//从大到小
//priority_queue<Type, Container, Functional>
void init()
{
while(!q.empty()) q.pop();
memset(in_deg,0,sizeof(in_deg));
memset(ans,0,sizeof(ans));
for(int i=1;i<=n;i++){
vec[i].clear();
}
}
void toposort()
{
for(int i=1;i<=n;i++)
{
if(in_deg[i]==0)
{
q.push(i);
}
}
int cnt=0;
while(!q.empty())
{
int u=q.top();q.pop();
ans[cnt++]=u;
for(int v=0;v<vec[u].size();v++)
{
in_deg[vec[u][v]]--;
if(in_deg[vec[u][v]]==0)
{
q.push(vec[u][v]);
}
}
}
if(cnt!=n) {
// cout<<"-1"<<endl;
printf("-1\n");
}else
{
// cout<<ans[0];
printf("%d",ans[cnt-1]);
for(int i=cnt-2;i>=0;i--)
printf(" %d",ans[i]);
// cout<<" "<<ans[i];
printf("\n");
}

}
int main()
{
// ios::sync_with_stdio(false);
int t;
scanf("%d",&t);
while(t--)
{
//cin>>n>>m;
scanf("%d%d",&n,&m);
init();
for(int i=0;i<m;i++)
{
//cin>>a>>b;
scanf("%d%d",&a,&b);
vec[b].push_back(a);//反向建图
//vec[a].push_back(b);
in_deg[a]++;
}
toposort();
}
return 0;
}
/*
1
3 1
3 1
------
3 1 2
*/
/*
在主要的拓扑排序的基础上又添加了一个要求:编号最小的节点要尽量排在前面;在满足上一个条件的基础上,编号第二小的节点要尽量排在前面;在满足前两个条件的基础上,编号第三小的节点要尽量排在前面……依此类推。(注意,这和字典序是两回事,不能够混淆。)
*/

/*
是反向建边,点大的优先级高,用拓扑排序+优先队列,逆向输出序列即可。

根据每对限制,可确定拓扑序列,但此时的拓扑序列可能有多个(没有之间关系的点的顺序不定)。本题要求较小的点排到前面,则可确定序列。

(1)如果点a和点b有直接和简接的拓扑关系,那么a和b的先后顺序可有拓扑排序确定。

(2)如果点a和点b没有直接和简接的拓扑关系,那么a和b的先后顺序由a和b所能到达的点的确定。

如:

1

3 2

3 1

3 1

应输出结果为 3 1 2

点3 和 点2 没有直接的拓扑关系,但是3到达最小点为1,2到达最小点为2。

综合(1)和(2)本题需要逆向处理。

PS:欧拉回路的路径输出也是逆向输出的。

*/

​POJ-3687​

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <queue>
#include <cstdio>
#define ll long long
#define Max 202
using namespace std;
int n,m,a,b;
int in_deg[Max],ans[Max];
vector<int> vec[Max];
priority_queue<int,vector<int>,less<int> > q;//从大到小
void init()
{
memset(ans,0,sizeof(ans));
memset(in_deg,0,sizeof(in_deg));
for(int i=1;i<=n;i++)
vec[i].clear();
while(!q.empty()) q.pop();
}
void toposort()
{
for(int i=1;i<=n;i++)
{
if(in_deg[i]==0) q.push(i);
}
int cnt=0;
int weigh=n;
while(!q.empty()){
int u=q.top(); q.pop();
cnt++;
ans[u]=weigh--;
for(int v=0;v<vec[u].size();v++)
{
in_deg[vec[u][v]]--;
if(in_deg[vec[u][v]]==0)
{
q.push(vec[u][v]);
}
}
}
if(cnt!=n)
printf("-1\n");
else
{
printf("%d",ans[1]);
for(int i=2;i<=cnt;i++)
printf(" %d",ans[i]);
printf("\n");
}

}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
init();
for(int i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
vec[b].push_back(a);
in_deg[a]++;
}
toposort();
}
return 0;
}
/*
有n个球,分别编号为1到n,并且每个球有对应的重量,输入时,每行输入的两个数,表示“编号”为a的球的重量小于“编号为b的球的重量”,分别输出1到n编号对应的球的重量,尽量保证编号较小的球重量也较小。注意仔细理解题:读入的时候是球的编号,输出的是对应编号的球的重量,看到dicuss区的数据才发现自己题目都读错了。
*/

/*
3

5 4
5 1
4 2
1 3
2 3

10 5
4 1
8 1
7 8
4 1
2 8


5 4
1 4
4 2
5 3
3 2
ans:
2 4 5 3 1 逆向建图
5 1 6 2 7 8 3 4 9 10 重边的话就输出 -1

标签小的重量尽量小

1~n编号的盒子,放1~n重量的球,给出m对盒子装的小球之间大小的关系,输出1~n编号盒子重量,要求编号越小的盒子,放的重量越小。
*/

END,BYEBYE!