拓扑排序:
定义:
在图论中,拓扑排序(Topological Sorting)是求一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:
1:每个顶点出现且只出现一次。
2: 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。
例如:
这个图的一个拓扑排序序列为:1-->2-->4-->3-->5
求拓扑序列的算法:
KAHN算法。
算法思想:
每次都找图中入度为零的点,然后将该点从图中删除,然后重复上述操作,直到图中没有顶点就结束。
算法流程:
用栈来实现这个算法。
步骤一:选取所有入度为零的点放入栈中去。
步骤二:从栈顶开始弹出顶点,每弹出一个顶点,将该顶点从图中删除。
步骤三:将新得到的图中的入度为零的点放入栈中。
步骤四:重复步骤二和步骤三,直到图中没有顶点了就结束算法。
通过拓扑排序,我们还可以判断图中是否存在环,具体操作看代码。
代码:
#include<bits/stdc++.h>
#define MAXN 9999
using namespace std;
int e[MAXN][MAXN];//邻接矩阵
int u,v;//顶点--->终点 无权值
int n,m;//n个顶点,m条边
int enter[MAXN];//enter[i]代表编号为i的顶点的入度是多少
int jieguo[MAXN];//存放一条拓扑排序的路径
int coun=0;//jieguo数组中顶点的个数,用于判断图中是否有环
int Kahn()
{
stack < int > q;//定义一个栈
for(int i=1;i<=n;i++)//遍历所有顶点,将所有入度为0的顶点都放入栈中去
if(!enter[i])
q.push(i);
while (!q.empty())
{
int z=q.top();//弹出栈顶元素,将该点在图中删除
q.pop();
jieguo[coun++]=z;//存储这个点
for(int i=1;i<=n;i++)//在图中删除这个点,所谓删除,就是将该顶点的所有出边点的入度减一
if(e[z][i])
{
enter[i]--;
if(!enter[i])//如果该顶点的出边点的入度减一后变成了0,那么将该出边点放入到栈中去
q.push(i);
}
}
if(coun<n)
{
cout<<"该图中存在环!"<<endl;
return 0;
}
return 1;
}
int main()
{
cin>>n>>m;
memset(enter,0,sizeof(enter));
memset(jieguo,0,sizeof(jieguo));
memset(e,0,sizeof(e));
for(int i=1;i<=m;i++)
{
cin>>u>>v;
e[u][v]=1;
enter[v]++;//初始化enter数组
}
if(Kahn()==1)
{
for(int i=0;i<coun-1;i++)
cout<<jieguo[i]<<"-->";
cout<<jieguo[coun-1];
}
}
例题:
例题一:【POJ-1094】
http://poj.org/problem?id=1094
题意:
给你一些大写字母间的偏序关系,然后让你判断能否唯一确定它们之间的大小关系,或者所给关系是矛盾的,或者到最后也不能确定它们之间的关系。如果能够确定它们之间的大小关系,就输出是在第几个偏序关系时确定的它们之间的大小关系,如果所给的关系是矛盾的,就输出是在第几个偏序关系时确定的它们之间的关系是矛盾的。
思路:
拓扑排序,因为题目要输出的是在第几组偏序关系时确定的它们之间的关系的,所以对于每次输入的偏序关系,我们都要进行一次拓扑排序,拓扑排序函数进行完成后返回的结果总共有三种:
第一种:得到了拓扑序列。
第二种:出现了矛盾(图中存在环)。
第三种:条件不足,无法确定它们之间的关系或者出现了矛盾。
只要拓扑排序函数返回的是第一二种情况,我们直接输出结果就可以了,对于后面输入的偏序关系就不用管了,如果拓扑排序函数返回的一直是第三种结果,那么我们还要对后面输入的偏序关系继续进行拓扑排序,如果所有的偏序关系都输入完成了,拓扑排序函数返回的还是第三种结果,说明我们到最后也不能确定它们之间的关系。
代码:
#include<algorithm>
#include<iostream>
#include<limits.h>
#include <sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<string>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<set>
#define mod 1000000007
#define MAXN 100
typedef long long ll;
using namespace std;
int e[MAXN][MAXN];//邻接矩阵
int JieGuo[MAXN];//保存路径的结果数组
int enter[MAXN];//入度数组
int j=0;
int n,m;
int Count=0;//记录进行拓扑排序的次数
char a,b,c;
int Kahn()
{
int Enter[MAXN];
for(int i=1;i<=n;i++)//不能用原enter数组,必须重新生成一个数组,因为后面的操作会改变数组中的值,如果用原enter数组,那么各个顶点的入度就乱了
Enter[i]=enter[i];
int ret=1;
stack < int > q;//定义一个栈
for(int i=1;i<=n;i++)//遍历所有顶点,将所有入度为0的顶点都放入栈中去
if(!Enter[i])
q.push(i);
while (!q.empty())
{
if(q.size()>1)//只要有2个或2个以上入度为0的点,说明当前无法确定它们之间的顺序
ret=0;
int z=q.top();//弹出栈顶元素,将该点在图中删除
q.pop();
JieGuo[j++]=z;//存储这个点
for(int i=1;i<=n;i++)//在图中删除这个点,所谓删除,就是将该顶点的所有出边点的入度减一
if(e[z][i])
{
Enter[i]--;
if(!Enter[i])//1如果该顶点的出边点的入度减一后变成了0,那么将该出边点放入到栈中去
q.push(i);
}
}
if(j<n)//出现了矛盾(图中出现了环)
return -1;//返回-1
return ret;//要么返回0,要么返回1,返回0代表无法确定它们之间的关系,返回1代表它们之间的关系可以确定了
}
int main()
{
while(1)
{
cin>>n>>m;
if(n==0&&m==0)
break;
memset(e,0,sizeof(e));
memset(enter,0,sizeof(enter));
Count=0;
int flag=0;
for(int i=0;i<m;i++)
{
cin>>a>>b>>c;
if(flag)
continue ;
e[a-'A'+1][c-'A'+1]=1;
enter[c-'A'+1]++;
memset(JieGuo,0,sizeof(JieGuo));
j=0;
Count++;
flag=Kahn();//每输入一个偏序关系,如果还没有确定它们之间的关系或者还没有推导出矛盾来,就继续执行拓扑排序
if(flag==1)//确定出他们之间的关系了
{
cout<<"Sorted sequence determined after "<<Count<<" relations: ";//输出是在第几个偏序关系时确定的
for(int i=0;i<j;i++)
{
char c=JieGuo[i]+'A'-1;
cout<<c;
}
cout<<'.'<<endl;
}
else if(flag==-1)//有矛盾
cout<<"Inconsistency found after "<<Count<<" relations."<<endl;
}
if(!flag)//到最后了也无法确定它们之间的关系
cout<<"Sorted sequence cannot be determined."<<endl;
}
}
例题二:【HDU-1285】
http://acm.hdu.edu.cn/showproblem.php?pid=1285
中文题。
思路:
运用拓扑排序,但是题目要求:
其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前 |
所以我们用优先级队列来代替栈结构,确保在正确输出时对于无法确定排名的队伍编号小的在前,编号大的在后。还有就是题目的输入数据可能存在重边,需要判断一下。这个题也就这一个需要注意的地方,其余都很简单。
代码:
#include<algorithm>
#include<iostream>
#include<limits.h>
#include <sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<string>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<set>
#define mod 1000000007
#define MAXN 1000
typedef long long ll;
using namespace std;
int e[MAXN][MAXN];
int JieGuo[MAXN];
int enter[MAXN];
int a,b;
int n,m;
int conn=0;
void Kahn()
{
priority_queue<int,vector<int>,greater<int> >q;//优先级队列,小的先出队
for(int i=1;i<=n;i++)
if(!enter[i])
q.push(i);
while(!q.empty())
{
int z=q.top();
q.pop();
JieGuo[conn++]=z;
for(int i=1;i<=n;i++)
{
if(e[z][i])
{
enter[i]--;
if(!enter[i])
q.push(i);
}
}
}
return ;
}
int main()
{
while(cin>>n>>m)
{
memset(e,0,sizeof(e));
memset(enter,0,sizeof(enter));
conn=0;
while(m--)
{
cin>>a>>b;
if(e[a][b])//题目可能存在重边,需要判断一下
continue;
e[a][b]=1;
enter[b]++;
}
Kahn();
for(int i=0;i<conn-1;i++)
cout<<JieGuo[i]<<" ";
cout<<JieGuo[conn-1]<<endl;
}
}
例题三【HDU-3342】:
题目:
给你一个图,让你判断图中是否有环
思路:
直接套模板,注意有重边
代码:
#include<algorithm>
#include<iostream>
#include<limits.h>
#include <sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<string>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<set>
#define mod 1000000007
#define MAXN 300
typedef long long ll;
using namespace std;
int n,m;
int u,v;
int Size=0;
int e[MAXN][MAXN];
int degree[MAXN];
bool Kahn()
{
queue < int >q;
for(int i=0;i<n;i++)
if(!degree[i])
q.push(i);
while(!q.empty())
{
int z=q.front();
q.pop();
Size++;
for(int i=0;i<n;i++)
{
if(e[z][i])
{
degree[i]--;
if(!degree[i])
q.push(i);
}
}
}
if(Size<n)
return false;
return true ;
}
int main()
{
while (cin>>n>>m)
{
memset(e,0,sizeof(e));
memset(degree,0,sizeof(degree));
Size=0;
if(m==0)
break;
while (m--)
{
cin>>u>>v;
if(e[u][v])//题目可能存在重边,需要判断一下
continue;
e[u][v]=1;
degree[v]++;
}
if(Kahn())
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
}
}
例题四【HDU-2647】:
http://acm.hdu.edu.cn/showproblem.php?pid=2647
题目:
老板要给很多员工发奖金, 但是部分员工有个虚伪心态, 认为自己的奖金必须比某些人高才心理平衡; 但是老板很人道, 想满足所有人的要求, 并且很吝啬,想花的钱最少
输入若干个关系
a b
a c
c b
意味着a 的工资必须比b的工资高 同时a 的工资比c高; c的工资比b高。
如果老板能够满足所有员工的要求,就输出老板需要花的最少的钱,如果不能够满足,就输出-1
思路:
拓扑排序
我们先抽象出图来,我们假设a和b之间有一条有向边b-->a,代表b的工资比a少,由于题目输入(a b)代表a的工资必须比b高,那么就是从b到a之间有一条有向边,b-->a,所以我们必须建立反向边。
我们定义数组ceng[i]代表节点i在拓扑序列的第几层,
通过邻接表来存图,顺便学一下如何在邻接表上建立反向图。
代码:
#include<algorithm>
#include<iostream>
#include<limits.h>
#include <sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<string>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<set>
#define mod 1000000007
#define MAXN 20005
typedef long long ll;
using namespace std;
int n,m;
int u[MAXN],v[MAXN];
int Size=0;
int first[10005],next1[MAXN];
int ceng[10005];
int enter[10005];
int Kahn()
{
queue < int >q;
for(int i=1;i<=n;i++)
if(!enter[i])
q.push(i);
while (!q.empty())
{
int z=q.front();
q.pop();
Size++;
int p=first[z];
while(p!=-1)
{
enter[u[p]]--;
if(!enter[u[p]])
{
q.push(u[p]);
ceng[u[p]]=max(ceng[u[p]],ceng[v[p]]+1);//这个地方不太会
}
p=next1[p];
}
}
if(Size!=n)
return -1;
int sum=0;
for(int i=1;i<=n;i++)
sum+=888+ceng[i];
return sum;
}
void fuzhi(int u,int v,int i)//在邻接表上建立反向图
{
next1[i]=first[v];
first[v]=i;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
memset(u,0,sizeof(u));
memset(v,0,sizeof(v));
memset(ceng,0,sizeof(ceng));
memset(enter,0,sizeof(enter));
memset(first,-1,sizeof(first));
memset(next1,0,sizeof(next1));
Size=0;
for(int i=1;i<=m;i++)
{
cin>>u[i]>>v[i];
fuzhi(u[i],v[i],i);
enter[u[i]]++;
}
cout<<Kahn()<<endl;
}
}