欧拉图

http://zh.wikipedia.org/wiki/%E4%B8%80%E7%AC%94%E7%94%BB%E9%97%AE%E9%A2%98

对于一个给定的连通图,通过图(无向图或有向图)中所有边一次且仅一次行遍图中所有顶点的通路称为欧拉通路,通过图中所有边一次并且仅一次行遍所有顶点的回路称为欧拉回路。具有欧拉回路的图称为欧拉图,具有欧拉通路而无欧拉回路的图称为半欧拉图

定理一

连通的无向图 G 有欧拉路径的充要条件是:G中奇顶点(连接的边数量为奇数的顶点)的数目等于0或者2。

连通的无向图 G 是欧拉的充要条件是:G中每个顶点没有奇顶点。

证明

  • 必要性:如果一个图能一笔画成,那么对每一个顶点,要么路径中“进入”这个点的边数等于“离开”这个点的边数:这时点的偶数。要么两者相差一:这时这个点必然是起点或终点之一。注意到有起点就必然有终点,因此奇顶点的数目要么是0,要么是2。
  • 充分性:
    1. 如果图中没有奇顶点,那么随便选一个点出发,连一个环C_1。如果这个环就是原图,那么结束。如果不是,那么由于原图是连通的,C_1 和原图的其它部分必然有公共顶点 s_1。从这一点出发,在原图的剩余部分中重复上述步骤。由于原图是连通图,经过若干步后,全图被分为一些环。由于两个相连的环就是一个环,原来的图也就是一个欧拉环了。
    2. 如果图中有两个奇顶点 u 和 v,那么加多一条边将它们连上后得到一个无奇顶点的连通图。由上知这个图是一个环,因此去掉新加的边後成为一条路径,起点和终点是 u 和 v。证毕。

连通无向图有欧拉路径的充要条件也可以写作“图中奇顶点数目不多于2个”,这是因为奇顶点数目不可能是1个。实际上,连通无向图中,奇顶点的数目总是偶数。对于不连通的无向图,如果有两个互不连通的部分都包含不止一条边,那么显然不能一笔画。只有当此图的边全都在某一个连通部分中即其它的连通部分都是一个个孤立的顶点,度数为0),并满足连通无向图关于一笔画的充要条件,而该图才能一笔画。也即是说,可以一笔画的(无向)图如果不是连通图,就必定是一个可以一笔画的连通图与若干个孤立顶点的组合。

除了用顶点的度数作为判定的充要条件,还可以用图中边的特性来作为欧拉回路存在的判定准则。连通的无向图 G中存在欧拉回路,等价于图G所有的边可以划分为若干个环的不交并。具体来说,等价于存在一系列的环C_1, C_2 , \cdots , C_m,使得图G里的每一条边都恰好属于某一个环。

定理二

如果连通无向图 G 有 2k 个奇顶点,那么它可以用 k 笔画成,并且至少要用 k 笔画成

证明:将这 2k 个奇顶点分成 k 对後分别连起,则得到一个无奇顶点的连通图。由上知这个图是一个环,因此去掉新加的边後至多成为 k 条欧拉路径,因此必然可以用 k 笔画成。但是假设全图可以分为 q 条欧拉路径,则由定理一知,每条链中只有不多于两个奇顶点,于是 2q \ge 2k。因此必定要 k 笔画成。

有向图的一笔画

对有向图来说,一笔画不仅指遍历所有边,而且要遵循正确的方向。严谨地说,一个连通有向图G有欧拉路径,指存在一个顶点,从它出发,沿着有向边的方向,可以不重复地遍历图中所有的边。有向图的欧拉回路则是指可以从某一顶点开始,沿有向边的方向不重复地遍历所有边,然后回到原来出发的顶点。用类似于定理一中证明的思路,可以得到有向图一笔画的判定准则:

  • 一个连通的有向图可以表示为一条从顶点uv的(不闭合的)欧拉路径的充要条件是:u的出度(从这个顶点发出的有向边的数量)比入度(指向这个顶点的有向边的数量)多1,v的出度比入度少1,而其它顶点的出度和入度都相等。
  • 一个连通的有向图是欧拉环的充要条件是以下两个之一:
    1. 每个顶点的出度和入度都相等;
    2. 存在一系列的(有向)环C_1, C_2 , \cdots , C_m,使得图G里的每一条边都恰好属于某一个环。

扩展
一笔画问题讨论的是能否不重复地遍历一个图的所有边,至于其中有否顶点的遍历或重复经过则没有要求。哈密顿问题讨论的则是顶点的遍历:能否不重复地遍历一个图的所有顶点?哈密顿问题由哈密顿1856首次提出,至今尚未完全解决

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int f[30],num[30],in[30],out[30];//分别用于并查集,入度,出度
void init() {
    int i;
    for( i=0; i<26; i++) {
        f[i]=i;
        num[i]=1;
    }
    memset(in,0,sizeof(in));
    memset(out,0,sizeof(out));
}
int findSet(int n) {
    if(f[n]!=n)
        f[n]=findSet(f[n]);

    return f[n];
}
void unionSet(int m,int n) {
    int p=findSet(m),q=findSet(n);
    if(p==q) return;
    if(num[p]>num[q]) {
        f[q]=p;
        num[p]+=num[q];
    } else {
        f[p]=q;
        num[q]+=num[p];
    }
}
int main() {
    int nCase,n,i,t1,t2;
    char s[1005];

    scanf("%d",&nCase);
    while(nCase--) {
        scanf("%d",&n);
        init();

        for(i=0; i<n; i++) {
            scanf("%s",s);
            t1=s[0]-'a',t2=s[strlen(s)-1]-'a';
            out[t1]++;
            in[t2]++;

            unionSet(t1,t2);
        }

        int flag1=1,flag2=1,flag3=1;

        for(i=0; i<26; i++) {
            if((out[i]||in[i])&&(f[i]!=f[t1])) {
                flag1=0;
                break;
            }
        }
        if(!flag1) {
            puts("The door cannot be opened.");
            continue;
        }

        int u=0,v=0;
        for(i=0; i<26; i++) {
            if(out[i]!=in[i]) {
                t1=out[i]-in[i];
                if(t1==1) {
                    u++;
                } else if(t1==-1) {
                    v++;
                } else {
                    flag3=0;
                    break;
                }
            }
        }
        if(!flag3) {
            puts("The door cannot be opened.");
            continue;
        }
        if((u+v==0)||(u==1&&v==1))
            puts("Ordering is possible.");
    }

}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值