紫书第六章-----数据结构基础(欧拉回路)

引言:首先是著名的七桥问题,欧拉解决了这个问题,从而掀开了图论的伟大篇章。

一个无向图存在欧拉回路,当且仅当该图所有顶点度数都为偶数,且该图是连通图。

一个有向图存在欧拉回路,所有顶点的入度等于出度且该图是连通图。

G为连通图,并且G仅有两个奇度结点(度数为奇数的节点)或者无奇度结点(欧拉回路)。
推论:当无向图G是有两个奇度的连通图时,G的欧拉通路必定以这两个结点为端点。

  • 有向图存在欧拉通路的充要条件(参考刘汝佳《算法竞赛入门经典》(第2版))

最多只能有两个点的入度不等于出度,而且必须是其中一个点的出度恰好比入度大1(作为起点),另一个的入度比出度大一(作为终点)。当然,还有一个前提条件:在忽略边的方向后(有向图的基图),图是连通的。

【例题6-16 Play on Words UVA - 10129 】

【代码一(dfs判连通性)】

/*
    本题相当于给了图的很多条边,比如abc,cef,fdb,相当于给了ac边,
    cf边,fb边。

    dfs判断图的连通性的思想:从某个出度非0的点出发遍历一次,如果
    有的点没遍历到,说明图不连通,否则连通
*/

#include<iostream>
#include<string>
#include<cstring>

using namespace std;

const int maxn=30;

int T,N;
string s;
int in[maxn],out[maxn];
bool graph[maxn][maxn];//图,两结点间是否有边
bool vis[maxn];

void dfs(int v){
    if(!vis[v]){
        vis[v]=1;
    }
    for(int i=0;i<maxn;i++){
        if(!vis[i] && graph[v][i]){
            dfs(i);
        }
    }
}

int main()
{
    cin>>T;
    while(T--){
        cin>>N;
        memset(graph,0,sizeof(graph));
        memset(in,0,sizeof(in));
        memset(out,0,sizeof(out));
        for(int i=0;i<N;i++){
            cin>>s;
            out[s[0]-'a']++;
            in[s[s.length()-1]-'a']++;
            graph[s[0]-'a'][s[s.length()-1]-'a']=1;
        }

        memset(vis,0,sizeof(vis));
        for(int i=0;i<maxn;i++){
            if(out[i] || in[i]){dfs(i);break;}//从某个非0度结点dfs即可,记住仅遍历一次
                    // 因为至少有两个字母,所以只需要保证入度为非0即可(出度必非0
        }

        bool flag1=1;
        for(int i=0;i<maxn;i++){
            if(in[i] && !vis[i]){flag1=0;break;}//如果存在非0度结点未被访问过,则为非连通图
            if(out[i] && !vis[i]){flag1=0;break;}//如果存在非0度结点未被访问过,则为非连通图
        }
        if(!flag1){
            cout<<"The door cannot be opened."<<endl;
            continue;
        }

        bool flag2=1;
        int n1=0,n2=0;
        for(int i=0;i<maxn;i++){
            if(in[i]!=out[i]){
                if(in[i]==out[i]+1) n1++;
                else if(in[i]+1==out[i]) n2++;
                else{
                    flag2=0;
                    break;
                }
            }
        }
        if(!((n1==1 && n2==1) || (n1==0 && n2==0))){
            flag2=0;
        }
        if(flag2) cout<<"Ordering is possible."<<endl;
        else cout<<"The door cannot be opened."<<endl;
    }
    return 0;
}

【代码二(并查集判连通性)】

/*
    本题其实是只有26个英文单词之间是否形成欧拉通路的问题。
    是否形成欧拉通路,首先用并查集判断基图是否是连通图,然后
    统计入度不等于出度的点的个数,如果只有两个点入度和出度不等,
    并且其中一个点入度比出度大1,另一个点出度比入度大1,那么便
    存在欧拉通路。
    并查集判断无向图是否连通的思想:把所有有边的点合并,最后如果
    所有的点都在一个集合里面,那么图是连通的,否则图不连通。
*/

#include <iostream>
#include<string>
#include<cstring>
using namespace std;

const int maxn=30;
string s;
int T,N;
int in[maxn],out[maxn];//记录某点的入度和出度
int par[maxn],ran[maxn];//并查集,par为父母结点,ran为每个元素的等级

//并查集判断是否是连通图
void init(){
    memset(ran,0,sizeof(ran));
    for(int i=0;i<maxn;i++){
        par[i]=i;
    }
}

int fin(int x){
    int tmp=x;
    while(tmp!=par[tmp]){
        tmp=par[tmp];
    }
    par[x]=tmp;//压缩路径
    return tmp;
}

void unio(int x,int y){
    x=fin(x);
    y=fin(y);
    if(ran[x]>ran[y]){
        par[y]=x;
    }
    else{
        if(ran[x]==ran[y]){
            ran[y]++;
        }
        par[x]=y;
    }
}

int main()
{
    cin>>T;
    while(T--){
        memset(in,0,sizeof(in));
        memset(out,0,sizeof(out));
        cin>>N;
        init();//刚开始忘记了初始化,就一直错!!!
        for(int i=0;i<N;i++){
            cin>>s;
            in[s[0]-'a']++;
            out[s[s.length()-1]-'a']++;
            unio(s[0]-'a',s[s.length()-1]-'a');
        }
        int cnt=0;
        for(int i=0;i<26;i++){
            if((in[i] || out[i]) && par[i]==i)  //cnt为1表明最终所有都落入同一个集合
                cnt++;                  //既然并查集最后只有一个集合,父母是自身的这种情形只能出现一次
        }
        bool flag=1;
        if(cnt!=1) flag=0;

        int n1=0,n2=0;
        for(int i=0;i<maxn;i++){
            if(!flag) break;
            if(in[i]!=out[i]){
                if(in[i]==out[i]+1) n1++;
                else if(in[i]+1==out[i]) n2++;
                else{
                    flag=0;
                    break;
                }
            }
        }

        if(!((n1==1 && n2==1) || (n1==0 && n2==0))) flag=0;

        if(flag) cout<<"Ordering is possible."<<endl;
        else cout<<"The door cannot be opened."<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值