图论:拓扑排序、欧拉路

设有一个有向无环图(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)条件。相关题目POJ1300POJ1386

//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&&deg[n]%2==1&&deg[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;
}

 

posted @ 2020-10-07 19:04  太山多桢  阅读(258)  评论(0)    收藏  举报