拓扑排序
定义:在一个有向图中,对所有的结点进行排序,要求没有一个结点指向它前面的结点
前提:有向无环图(DAG)
关键点:有向图、且不能形成环
算法描述:deg[100005]用来统计点i的度数(入度)
把所有入度为0的点插入到队列里
while(队列不空)
取出队首点P
访问P的所有相邻点Q,把它们的入度减一(就相当于把点P删了)
如果Q入度被删成0了,点Q入列
我们边做循环,边统计结点们出队的顺序,这个出队顺序就是一个拓扑序,如果最后拓扑序不等于结点数,则不存在拓扑序
例题(HihoCoder - 1174 )
问题描述
我们都知道大学的课程是可以自己选择的,每一个学期可以自由选择打算学习的课程。唯一限制我们选课是一些课程之间的顺序关系:有的难度很大的课程可能会有一些前置课程的要求。比如课程A是课程B的前置课程,则要求先学习完A课程,才可以选择B课程。大学的教务收集了所有课程的顺序关系,但由于系统故障,可能有一些信息出现了错误。现在小Ho把信息都告诉你,请你帮小Ho判断一下这些信息是否有误。错误的信息主要是指出现了"课程A是课程B的前置课程,同时课程B也是课程A的前置课程"这样的情况。当然"课程A是课程B的前置课程,课程B是课程C的前置课程,课程C是课程A的前置课程"这类也是错误的。
输入
第1行:1个整数T,表示数据的组数T(1 <= T <= 5)
接下来T组数据按照以下格式:
第1行:2个整数,N,M。N表示课程总数量,课程编号为1…N。M表示顺序关系的数量。1 <= N <= 100,000. 1 <= M <= 500,000
第2…M+1行:每行2个整数,A,B。表示课程A是课程B的前置课程。
输出
第1…T行:每行1个字符串,若该组信息无误,输出"Correct",若该组信息有误,输出"Wrong"。
Sample Input
2
2 2
1 2
2 1
3 2
1 2
1 3
Sample Output
Wrong
Correct
关键点就在于判断这个图能否形成环,也就是判断这个图能否形成拓扑序
Code:
#include<iostream>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
int t, n, m;
queue<int> qu;
vector<int> vv[100005];
int deg[100005];
bool vis[100005];
bool toposport() {
int num = 0;//已经排序好的点的个数
while (!qu.empty()) {
int now = qu.front();
qu.pop();
num++;
for (auto to : vv[now]) {
if (vis[to]) {//经过我的测试,这里也可以没有
continue;
}
else {
deg[to]--;
if (!deg[to]) {
vis[to] = 1;//标记它是个入度为0的点
qu.push(to);
}
}
}
}
if (num == n) {
return 1;
}
else {
return 0;
}
}
int main() {
cin >> t;
while (t--) {
memset(deg, 0, sizeof(deg));
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= 100000; i++) {
vv[i].clear();
}
while (!qu.empty()) {
qu.pop();
}
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int a, b;
cin >> a >> b;
vv[a].push_back(b);
deg[b] ++;//a指向b,那么b的入度自然要加一
}
for (int i = 1; i <= n; i++) {
if (!deg[i]) {//如果入度为0,加入队列
vis[i] = 1;
qu.push(i);
}
}
if (toposport()) {
cout << "Correct" << endl;
}
else {
cout << "Wrong" << endl;
}
}
}
例题2:HDU-2094
有一群人,打乒乓球比赛,两两捉对撕杀,每两个人之间最多打一场比赛。
球赛的规则如下:
如果A打败了B,B又打败了C,而A与C之间没有进行过比赛,那么就认定,A一定能打败C。
如果A打败了B,B又打败了C,而且,C又打败了A,那么A、B、C三者都不可能成为冠军。
根据这个规则,无需循环较量,或许就能确定冠军。你的任务就是面对一群比赛选手,在经过了若干场撕杀之后,确定是否已经实际上产生了冠军。
Input
输入含有一些选手群,每群选手都以一个整数n(n<1000)开头,后跟n对选手的比赛结果,比赛结果以一对选手名字(中间隔一空格)表示,前者战胜后者。如果n为0,则表示输入结束。
Output
对于每个选手群,若你判断出产生了冠军,则在一行中输出“Yes”,否则在一行中输出“No”。
Sample Input
3
Alice Bob
Smith John
Alice Smith
5
a c
c d
d e
b e
a d
0
Sample Output
Yes
No
这题以前用set能过,后来学习了拓扑排序,发现这个题满足拓扑排序的特征,故重写一遍
在这里插入代码片#include<iostream>
#include<map>
#include<queue>
#include<cstring>
using namespace std;
vector<int> vv[1005];
bool book[1005];
int dis[1005];
int n;
map<string,int> mp;
queue<int> qu;
bool toposort(){
int cnt=0;
while(!qu.empty()){
int now=qu.front();
qu.pop();
cnt++;
if(book[now]){
continue;
}
for(auto i:vv[now]){
dis[i]--;
if(dis[i]==0){
book[i]=1;
qu.push(i);
}
}
}
if(cnt==1){
return 1;
}else{
return 0;
}
}
int main(){
while(scanf("%d",&n)!=EOF){
memset(book,0,sizeof(book));
memset(dis,0,sizeof(dis));
if(n==0){
break;
}
for(int i=1;i<=n;i++){
vv[i].clear();
}
mp.clear();
int id=1;
for(int i=1;i<=n;i++){
string name1,name2;
cin>>name1>>name2;
if(!mp.count(name1)){
mp[name1]=id++;
}
if(!mp.count(name2)){
mp[name2]=id++;
}
vv[mp[name1]].push_back(mp[name2]);
//cout<<mp[name1]<<"--->"<<mp[name2]<<endl;
dis[mp[name2]]++;
}
for(int i=1;i<=mp.size();i++){
if(!dis[i]){
book[i]=1;
qu.push(i);
}
}
if(toposort()){
cout<<"Yes"<<endl;
}else{
cout<<"No"<<endl;
}
}
}