ACM-图论-拓扑排序

来源:互联网 发布:辛翠英诊所网络预约 编辑:程序博客网 时间:2024/04/28 02:04

拓扑排序用于解决图论中有向图的一类序列问题。即在某一个有向图graph中,假设每一条有向边(u,v)代表节点u必须排在节点v的前面,那么按照这样的规则,将所有的节点进行排序,最终得出的序列就称为拓扑序。拓扑排序在ACM比赛和实际生活中都比较常见,只要能将事物抽象成有向图,并要求按规则排序,那么就可以考虑拓扑排序,比如选修课程的安排、按胜负排名次等。


拓扑排序只适用于有向无环图,所以使用拓扑排序的第一步就是先将问题抽象成有向图,进行图的初始化、建立等工作,然后运行拓扑排序算法即可。

然后考虑如何实现拓扑排序。按照其原始的定义,边(u,v)代表u排在v前面,也就是说没有边指向的节点拓扑序较小,所以拓扑序为1的节点应该是入度为0的点,再考虑拓扑序为2的节点,它最多只应该是拓扑序唯一指向的节点,而不能由其它节点指向它,否则它的拓扑序不为2,那这样看来该节点不就是删除以拓扑序为1的节点出发的边后,入度为0的节点了么。综上,拓扑排序算法的大题思路就是:每次选出入度为0的节点,然后删除该节点以及由该节点出发的边,调整其它节点的入度,然后再选出入度为0的节点,相同的处理方法,依次类推,直到没有入度为0的节点为止。那么,每次选出节点的顺序就是拓扑序。

......

最后考虑一下细节,如果图用邻接表来存储的话,那么入度信息就可以使用第一个元素维护,并且重边对拓扑序无影响,因为删除节点的时候同样会删除重边,还有就是拓扑排序运行的最终结果就是为每一个节点都分配了拓扑序号,所有节点维护的入度信息都被更新为了0,否则说明图中存在环。

这里有一道关于拓扑排序的常见应用的题,HDOJ:1285,时空转移(点击打开链接),题目如下:

确定比赛名次

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 16595    Accepted Submission(s): 6566


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 31 22 34 3
 

Sample Output
1 2 4 3
 

Author
SmallBeer(CML)
 

Source
杭电ACM集训队训练赛(VII)
 

Recommend
lcy   |   We have carefully selected several similar problems for you:  2647 3342 1548 1102 1874 
 

题意:

给出比赛的输赢结果,要求赢得排在输的前面,然后要求以此对所有的队伍进行排序。

分析:

可以讲队伍看成节点,然后将输赢看成节点间的一条边,这样就将问题抽象成了图,那么问题就转化成了拓扑排序。

源代码:

#include <cstdio>#include <vector>#include <queue>#include <algorithm>using namespace std;const int MAXN = 505;vector<int> Graph[MAXN];int TopNum[MAXN], NodeNum[MAXN];;int NumVertex, NumEdge;// 有向无环图一定存在拓扑序void TopSort(){    // 维护入度为0的节点,编号从小到大排序,保证编号小的节点的拓扑序小    priority_queue<int, vector<int>, greater<int> > que;    // 将入度为0的节点加入队列    for(int i=1; i<=NumVertex; ++i)    {        if(Graph[i][0] == 0) que.push(i);    }    // 循环处理入度为0的节点,并赋予拓扑序    int cnt = 0;    while(!que.empty())    {        int u = que.top();        que.pop();        // 将最较小拓扑序号赋给入度为0且当前编号最小的节点        TopNum[u] = ++cnt;        // 删除节点u出发的边,并调整其它节点的入度        for(int i=1; i<Graph[u].size(); ++i)        {            // 将调整后入度为0的节点加入队列            if(--Graph[Graph[u][i]][0] == 0) que.push(Graph[u][i]);        }    }    // 图中存在环则无拓扑序    if(cnt != NumVertex) return;    // 如果图并不一定是全联通的,那么判原图的某一连通域中是否存在环:    for(int i=1; i<=NumVertex; ++i) if(Graph[i][0]) puts("somerwhere of the graph has a cycle");    // 输出以拓扑序排列的节点编号    for(int i=1; i<=NumVertex; ++i) NodeNum[TopNum[i]] = i;    for(int i=1; i<=NumVertex; ++i) printf("%d%c", NodeNum[i], i==NumVertex?'\n':' ');}int main(){//freopen("sample.txt", "r", stdin);    while(~scanf("%d%d", &NumVertex, &NumEdge))    {        // 初始化        for(int i=1; i<=NumVertex; ++i)        {            Graph[i].clear();            // 初始化入度            Graph[i].push_back(0);        }        // 建图        for(int i=1; i<=NumEdge; ++i)        {            int u, v;            scanf("%d%d", &u, &v);            // 使用邻接表时,重边对于拓扑序无影响,所以可以不用处理            //if(find(Graph[u].begin(), Graph[u].end(), v)            //   == Graph[u].end())            {                Graph[u].push_back(v);                // v节点的入度加1                ++Graph[v][0];            }        }        // 拓扑排序        TopSort();    }    return 0;}

其它相关的题目还有,HDOJ:1811,UESTC:1150。

0 0
原创粉丝点击