拓扑排序复习笔记

今日在健身房撸铁的时候突然想起拓扑排序,今天就来做个复习笔记


题目引子

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的话是不能输出的 ,一些小细节还是要注意一下的。


# 总结 今天主要是对拓扑排序做了一个复习整理的笔记,以后忘了的话还可以回来复习一下,也希望对看到的同学做到一个帮助。

猜你喜欢

转载自blog.csdn.net/DAVID3A/article/details/114328439