图论:拓扑排序、欧拉路
设有一个有向无环图(DAG图),对其进行拓扑排序即求其中节点的一个拓扑序列,对于所有的有向边(U,V),在该序列中节点U都排在节点V之前。满足该要求的节点序列,被称为满足拓扑次序的序列
拓扑次序将图限定在有向无环图上。若无向或是有环,各节点之间的先后关系不能确定。
简单描述一下拓扑排序的算法:从图中扫描出来入度为0的节点,放入栈或者队列中。依次取出节点,将从当前节点伸出去的边都去掉,由此相关的节点入度会减小。若相关节点入度减为0,将该节点放入栈或队列中等待被处理。
拓扑排序也是一个图论中经常讨论的问题,但是难度不大。主要的难点还是在于分析问题,抓取问题特征将其转化为图论进行处理。目前见到的拓扑排序的应用:
1)整理排序。处理元素间有相互依赖关系的排序问题。比如洛谷CF977D
2)判断图是否是有向无环图。若不能拓扑排序成功说明有环。
拓扑排序的编程需要存放边、入度、节点的栈/队列,因此需要用的结构还是vector和queue。下方为示例代码,题目为洛谷CF977D
#include<bits/stdc++.h>
using namespace std;
int n;
long long num[105],ans[105];
vector<int> edges[105];
int inDegree[105];
queue<int> q;
int main()
{
while(scanf("%d",&n)!=EOF){
for(int i=1;i<=n;i++){
scanf("%lld",&num[i]);
edges[i].clear();
inDegree[i]=0;
}
while(!q.empty()){
q.pop();
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(num[i]*2==num[j]){
edges[i].push_back(j);
inDegree[j]++;
}
else if(num[i]==num[j]*3){
edges[i].push_back(j);
inDegree[j]++;
}
}
}
for(int i=1;i<=n;i++){
if(inDegree[i]==0){
q.push(i);
}
}
int cnt=0;
while(!q.empty()){
int now=q.front();
q.pop();
cnt++;
ans[cnt]=num[now];
for(int i=0;i<edges[now].size();i++){
inDegree[edges[now][i]]--;
if(inDegree[edges[now][i]]==0){
q.push(edges[now][i]);
}
}
}
for(int i=1;i<=n;i++){
if(i!=1){
printf(" ");
}
printf("%lld",ans[i]);
}
printf("\n");
}
return 0;
}
欧拉路是指从图中任意一点开始到图中任意一点结束的路径,并且图中每条边通过且只通过一次。欧拉回路是指起始点和终止点相同的欧拉路。
无向图存在欧拉路的条件:1)所有点度都是偶数,任意点可作为起点 2)有两个点是奇数点度,其余为偶数点度,奇数点必为起点
有向图存在欧拉路的条件:1)每个点的入度等于出度,任意点可作为起点 2)有一个点的出度比入度大1,另一个点入度比出度大1,其余点出入度相等,前者作为起点
以上两种条件被用于判断欧拉回路/欧拉通路是否存在。欧拉回路要求1)条件,通路要求2)条件。相关题目POJ1300、POJ1386
//POJ1300
#include<stdio.h>
#include<string.h>
int root[10000],deg[10000];
int findroot(int x){
if(root[x]==-1) return x;
return root[x]=findroot(root[x]);
}
int main(){
char str[10000];
int n,m;
while(gets(str)){
if(str[0]=='E') break;
int cnt=0;
sscanf(str,"START%d%d",&n,&m);
for(int i=0;i<m;i++){
root[i]=-1;deg[i]=0;
}
for(int i=0;i<m;i++){
gets(str);
if(strlen(str)==0) continue;
int to=0;
for(int j=0;j<=strlen(str);j++){
if(str[j]==' '||str[j]==0){
deg[i]++;deg[to]++;cnt++;
if(findroot(i)!=findroot(to)){
root[findroot(to)]=findroot(i);
}
to=0;
}
else{
to=to*10+str[j]-'0';
}
}
}
gets(str);
int num=0;
if(findroot(n)!=findroot(0)){printf("NO\n");continue;}
for(int i=0;i<m;i++){
if(deg[i]>0&&findroot(i)==i) num++;
}
if(num>1){printf("NO\n");continue;}
int res=0;
for(int i=0;i<m;i++){
if(deg[i]%2==1) res++;
}
if(n==0&&res==0) printf("YES %d\n",cnt);
else if(n!=0&&res==2&°[n]%2==1&°[0]%2==1) printf("YES %d\n",cnt);
else printf("NO\n");
}
}
求欧拉路算法的主要思路是将图DFS遍历一遍,把沿途经过的边倒序输出就是一条欧拉路。P1341,如果还要求输出的欧拉路字典序最小,按照DFS的特点从小遍历可走的边即可。欧拉路题目中用邻接矩阵存储比较好,因为要标识已经走过的路,而不是经过的点。
#include<bits/stdc++.h>
using namespace std;
int n,s,t;
int edge[1000][1000];
int deg[1000];
char ans[10000];
void dfs(int b)
{
for(int i='A';i<='z';i++){
if(edge[i][b]){
edge[b][i]=edge[i][b]=0;
dfs(i);
}
}
ans[n--]=b;
}
int main()
{
scanf("%d",&n);
char a,b;
for(int i=1;i<=n;i++){
cin>>a>>b;
edge[a][b]=edge[b][a]=1;
deg[a]++;
deg[b]++;
}
int num=0;
for(int i='A';i<='z';i++){
if(deg[i]>0){
if(deg[i]%2==1){
num++;
if(!s){
s=i;
}
}
}
}
if(!s){
for(int i='A';i<'z';i++){
if(deg[i]){
s=i;
break;
}
}
}
if(num&&num!=2){
printf("No Solution\n");
return 0;
}
dfs(s);
cout<<ans<<endl;
return 0;
}


浙公网安备 33010602011771号