今日在健身房撸铁的时候突然想起拓扑排序,今天就来做个复习笔记
题目引子
HDU-1285
确定比赛名次
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 55040 Accepted Submission(s): 20319
Problem Description
有N个比赛队(1<=N<=500),编号依次为1,2,3,。。。。,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得每个队的比赛成绩,只知道每场比赛的结果,即P1赢P2,用P1,P2表示,排名时P1在P2之前。现在请你编程序确定排名。
Input
输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示队伍的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即P1队赢了P2队。
Output
给出一个符合要求的排名。输出时队伍号之间有空格,最后一名后面没有空格。
其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。
Sample Input
4 3
1 2
2 3
4 3
Sample Output
1 2 4 3
一、拓扑是什么?
拓扑排序的定义
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
二、执行步骤
拓扑排序算法主要是循环执行以下两步,直到不存在入度为0的顶点为止。
(1) 选择入度为0的结点然后输出它;
(2) 从网中删除此结点及所有出边;
循环结束后,如果输出的结点数小于网中的结点数,则输出“有回路”信息,否则输出的结点序列是一种拓扑序列。 [2]
三、AC代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int mp[550][550];
int ans[550],in[550];
void tp(){
//寻找有点指向的点,并进行存储
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(mp[i][j])in[j]++;
for(int i=1;i<=n;i++){
int temp = 1;
while(in[temp]!=0)temp++;
ans[i] = temp;
in[temp] = -1;
//切除和temp有关的所有点
for(int j=1;j<=n;j++)
if(mp[temp][j])in[j]--;
}
}
int main(){
while(~scanf("%d%d",&n,&m)){
memset(in, 0, sizeof in);
//ans数组用来存储排序后的结果
memset(ans, 0, sizeof ans);
memset(mp, 0, sizeof mp);
int x,y;
for(int i=0;i<m;i++){
cin >> x >> y;
mp[x][y] = 1;
}
tp();
for(int i=1;i<=n;i++){
cout << ans[i];
if(i!=n)cout << " ";
else cout << endl;
}
}
return 0;
}
四、算法演示
我们可以注意到,拓扑排序算法首先是图里找到入度为0的结点(什么是入度为0呢?你可以理解成是没有箭头指向它的结点),这里我们找到的第一个是a,然后从网中删除此顶点及所有出边,变成了b图,同时输出a,这样以此类推就可以输出拓扑排序了
拓展题
ZCMU OJ上有一个相似题目,但需要做一些改动
ZCMU-2103
2103: 士兵排队问题
Time Limit: 1 Sec Memory Limit: 128 MB
Submit: 103 Solved: 33
[Submit][Status][Web Board]
Description
有N个士兵(1≤N≤26),编号依次为 A,B,C,…,队列训练时,指挥官要把一些士兵从高到矮一次排成一行,但现在指挥官不能直接获得每个人的身高信息,只能获得“P1比P2高”这样的比较 结果(P1、P2∈A,B,C,…,Z,记为 P1>P2),如”A>B”表示A比B高。
请编一程序,根据所得到的比较结果求出一种字典序最小的排队方案。
(注:比较结果中没有涉及的士兵不参加排队)
Input
比较结果从文本文件中读入(文件由键盘输入),每个比较结果在文本文件中占一行。
Output
若输入数据无解,打印“No Answer!”信息,否则从高到矮一次输出每一个士兵的编号,中间无分割符,并把结果写入文本文件中,文件由键盘输入:
Sample Input
A>B
B>D
F>D
Sample Output
ABFD
AC代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int mp[50][50],ans[50],in[50];
int cnt,mx = -1;
set<int>s;
void tp(){
for(int i=1;i<=mx;i++)
for(int j=1;j<=mx;j++)
if(mp[i][j])in[j]++;
// for(int i=1;i<=mx;i++) cout << " "<< in[i];
//cout << endl;
for(int i=1;i<=cnt;i++)
{
int temp = 1;
while(temp<=mx){
这里我们需要确保temp存在于s中
if((std::find(s.begin(), s.end(), temp) != s.end())&&in[temp]==0)break;
temp++;
}
// cout << "text->" << temp << endl;
ans[i] = temp;
in[temp] = -1;
for(int j=1;j<=mx;j++)
if(mp[temp][j])in[j]--;
}
}
int main()
{
char a,b,c;
int x,y,m;
while(cin>>a>>c>>b) {
//cout << a << " " << c <<" " << b << endl;
x = a - 'A' + 1;
y = b - 'A' + 1;
mx = max(mx, x);
mx = max(mx, y);
//这里采用了set集合来统计出现多少个不重复的数据
s.insert(x);
s.insert(y);
if (c == '>')mp[x][y] = 1;
else mp[y][x] = 1;
}
// for(set<int>::const_iterator p=s.begin();p!=s.end();++p)
// cout << *p << " ";
// cout << endl;
cnt = s.size();
// cout << mx << endl;
// cout << cnt << endl;
tp();
for(int i=1;i<=mx;i++) {
if (in[i] > 0) {
//若最终还存在入度大于0 的顶点,则无解
cout << "No Answer!\n";
return 0;
}
}
for(int i=1;i<=cnt;i++) {
if (i != cnt)printf("%c", ans[i]+'A'-1);
else printf("%c\n",ans[i]+'A'-1);
}
return 0;
}
这里有几个难点,首先是要将字母数据转换为数字,然后就是像样例这样没有C、E的话是不能输出的 ,一些小细节还是要注意一下的。
# 总结 今天主要是对拓扑排序做了一个复习整理的笔记,以后忘了的话还可以回来复习一下,也希望对看到的同学做到一个帮助。