之前一直不知道拓扑排序为何物。今天看了一天的拓扑排序,顺便做了两道poj上的相关简单题。下面相当于帮助新手入门吧(假设读者已经看过相关的基础概念和理论)。PS:本文着重讲实现。
拓扑排序动画演示:http://www.jcc.jx.cn/xinwen3/news/kj/flash/2009/0426/1302.htm
先结合问题来分析吧。
先看poj 2367
传送门:http://poj.org/problem?id=2367
这是拓扑排序最最基础的问题。
题目大意是,N个火星人按1~N编号,然后给出父母孩子关系(BT的火星人可以有任意数量父母,这里都不该称父母了。。。),然后这N个人参加火星议会,议会上这每个人要发言,要你安排一个发言顺序使得长辈要先于年轻人发言。
这就是一裸的拓扑排序。
实现方法:
拓扑排序的实现方式之一就是通过邻接矩阵来表示图,然后遍历矩阵来增加每个火星人的入度。之后把入度为0的火星人编号加入一队列。。。然后。。。
算了还是写流程吧。。。
1.建立邻接矩阵。
2.初始化各点(即编号)的入度数(indegree)。
3.把度数为0的点放入队列q中。标记该点已被选入最终序列。
4.只要队列不空,弹出元素t,把t加入最终答案序列。把与t相连的点的度数-1。之后把度数为0并且没有被标记的点加入队列并标记之。
这就是通过临界表和队列来实现拓扑排序。这个实现过程和上面的动画演示本质上是一样的。
CODE:
#include<iostream>
#include<queue>
#include<memory.h>
#include<fstream>
using namespace std;
int map[101][101];
int indegree[101];
int ans[101];
bool vis[101]; //标记数组
int N,len;
queue<int> q;
int main()
{
//ifstream cin("in.txt");
while(cin>>N)
{
memset(vis,false,sizeof(vis));
memset(indegree,0,sizeof(indegree));
len=1;
for(int i=1;i<=N;i++) //初始化邻接矩阵和每个点得入度
for(int j=1;j<=N;j++)
{
cin>>map[i][j];
if(map[i][j]==0)
break;
else
indegree[map[i][j]]++;
}
for(int i=1;i<=N;i++)
if(indegree[i]==0)
{
q.push(i);
vis[i]=true;
}
while(!q.empty())
{
int t=q.front();
q.pop();
ans[len++]=t;
for(int i=1;i<=N&&map[t][i];i++)
indegree[map[t][i]]--;
for(int i=1;i<=N;i++)
if(indegree[i]==0&&!vis[i])
{
q.push(i);
vis[i]=true;
}
}
for(int i=1;i<N;i++)
cout<<ans[i]<<" ";
cout<<ans[N]<<endl;
}
return 0;
}
下面来看稍难一些的第二题。
传送门:http://poj.org/problem?id=1094
这题就比较头疼。用到了拓扑排序的一些性质。
题目大意是,你输入n,m,然后输入m组用大写字母表的前n个字母组成的小于不等式。比如A<B,B<C等。要让你求出由前几组就能唯一确定一“递增”序列或有矛盾。一旦前几组能判定就无视后面的不等式,即便你在前几组唯一确定一序列而后面的不等式又使之矛盾。要是直到最后都不能唯一确定一序列就输出不能确定。
先展示一下拓扑排序的特点和一些性质。
1.按照拓扑排序算法对一个图进行处理后,若最后得到的序列点的个数小于原图中的总点数。那说明原图有环。
证明:
该算法每次要取入度为0的点加入最终序列,而环上每个点的入度都不为0,那么要使环上的点入度为0那就必须要先取走环上的某点,而要去走该点的前提是该点入度为0,但是该点在环上,入度不可能为0。故环上的点全都加不到最终序列中去。所以最终序列里的点要少于总点数。
其实这也很好想,如果A课程要先于B修掉而B课程又要先于A修掉,那就不可能有合法序列存在,所以拓扑排序是解决不了这个问题的。
2.如果每次有多个点得入度为0 是 最终序列不唯一的充要条件。
这个很明显。
知道了以上两个性质再来解决这题就要方便一些了。
由于题目要你求前多少组不等式就能判定,那你每输入一组不等式就要进行一次拓扑排序来判定是否有矛盾或是序列唯一或是无法判定。要是中途现可以判定的情况就不要再拓扑排序了,直接无视后面的不等式。
CODE:
#include<iostream>
#include<memory.h>
#include<queue>
#include<fstream>
using namespace std;bool map[26][26];
bool vis[26]; //标记数组
char s[3];
int indegree[26];
char ans[27];
int n,m,len;int topsort()
{
len=0;
memset(vis,false,sizeof(vis));
memset(indegree,0,sizeof(indegree));
int c=0;
bool determined=true;
queue<int> q;for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(map[i][j])
indegree[j]++;for(int i=0;i<n;i++)
if(indegree[i]==0)
{
q.push(i);
vis[i]=true;
c++;
}
if(c>1)
determined=false;while(!q.empty())
{
int t=q.front();
q.pop();
ans[len++]=t;for(int i=0;i<n;i++)
if(map[t][i])
indegree[i]--;c=0;
for(int i=0;i<n;i++)
if(indegree[i]==0&&!vis[i])
{
q.push(i);
vis[i]=true;
c++;
}
if(c>1)
determined=false;
}
if(len<n)
return 0; //代表判定矛盾
if(determined)
return 1; //代表判定有唯一序列
else
return 2; //代表无法判定
}int main()
{
//ifstream cin("in.txt");
//ofstream cout("out.txt");
while(cin>>n>>m && !(m+n==0))
{
memset(map,false,sizeof(map));
int min=0,judge=-1;for(int i=0;i<m;i++)
{
cin>>s[0]>>s[1]>>s[2];
map[s[0]-'A'][s[2]-'A']=true;
if(judge!=0&&judge!=1)
{
min++;
judge=topsort();
}
}
if(judge==0)
cout<<"Inconsistency found after "<<min<<" relations."<<endl;
else
if(judge==1)
{
for(int i=0;i<n;i++)
ans[i]+='A';
cout<<"Sorted sequence determined after "<<min<<" relations: ";
for(int i=0;i<n;i++)
cout<<ans[i];
cout<<"."<<endl;
}
else
cout<<"Sorted sequence cannot be determined."<<endl;
}
return 0;
}
附上本题的一些测试数据和答案:http://poj.org/showmessage?message_id=133905
poj 2367 & poj 1094 分析&解题报告(拓扑排序入门)
最新推荐文章于 2018-11-14 10:54:38 发布
