6.4.4 用 dfs 求欧拉回路 (UVa 10129)

我的未来我决定 提交于 2020-01-23 14:13:59

欧拉回路:该回路遍历了一个图中所有的边,并且每条边只遍历一次。(一笔画)

欧拉路径:从起点开始到终点,遍历了图中所有的边,并且每条边只遍历一次。

度数:一个点连接了几条边。

入度和出度分别指:进入该点的边的数量,走出该点的边的数量。

连通无向图存在欧拉回路的充要条件:所有点的度数都为偶数。

连通无向图存在欧拉路径的充要条件:仅存在两个度数为奇数的点,其他点的度数都为偶数。(这两个度数为奇数的点,一个为奇数,一个为偶数)

连通有向图存在欧拉回路的充要条件:对于所有的点,入度等于出度。

连通有向图存在欧拉路径的充要条件: 仅存在两个点,其中一个点的入度比出度大一,另一个店的出度比入度大一。(出度大的为起点,入度大的为终点)

 

根据连通性和度数可判断出无向图和有向图是否存在欧拉回路和欧拉路径,可用 dfs 构造欧拉回路和欧拉路径。 

 

基本思路:使用 dfs 的方式,遍历图中所有的点。dfs 一个环,然后在回溯的过程中,可能会遇到一个公共点连接着另一个环,此时再对这个公共点进行 dfs 遍历另一个环…… 如此递归。在 dfs 完与 u 结点相连的 v 结点后,再将 u,v 这条边压入输出栈(采用逆序的顺序输出结果,因为 dfs 栈帧入栈的顺序与出栈的顺序相反,起始点最后才出栈)。

例如:用 dfs 求该图的欧拉路径

dfs 遍历该图的顺序为: (1, 2) (2, 3) (3, 4) (4, 5) (此时 dfs(5) 出栈,dfs(6) 入栈) (4, 6) (6, 7) (7, 2) (2, 8) (8, 4) (此时 dfs(4) 出栈,开始回溯)

dfs 栈帧出栈后,边压入输出栈的顺序为: (4, 5) (8, 4) (2, 8) (7, 2) (6, 7) (4, 6) (3, 4) (2, 3) (1, 2)

从输出栈的栈顶开始输出,输出结果为: (1, 2) (2, 3) (3, 4) (4, 6) (6, 7) (7, 2) (2, 8) (8, 4) (4, 5)

 

核心代码:

void dfs(int u){
    for(int v=0; v<n; v++){                   
        if(node[u][v] && !vis[u][v]){         //若存在u,v边,并且没有访问过
            vis[u][v] = vis[u][v] = 1;        //表示已经访问过
            dfs(v);                           //dfs v 结点,继续寻找回路。
            output.push(Node(u, v));          //dfs(v) 出栈后,将u,v边压入输出栈
        }
    }
}

 

注意:一定要在 dfs(v) 执行完之后再将 u,v 边压入输出栈。

错误代码:

cout << u << " " << v << endl;
dfs(v);

如果在 dfs(v) 执行之前就输出 u,v 边,则有可能输出的边不能构成一笔画。原因:v 结点可能是终点(该节点不再有边能走),而中间可能会有多个环。如果先输出了 u,v 边,栈中的 dfs(u) 可能还会再访问其他的边,这时再访问其他的边时,是输出从 u 结点到其他结点,而不是从 v 结点到其他节点。

例如:以1为起点

 

3 为两个环的公共点。

若采用错误代码,则会出现输出 3, 4 后,输出 3, 5 这样的错误结果。

使用核心代码,dfs 栈帧出栈后,边压入输出栈的顺序为:(3, 4) (8, 3) (7, 8) (2, 7) (6, 2) (5, 6) (3, 5) (2, 3) (1, 2),输出从输出栈的栈顶开始,逆序输出。

输出的结果为:

  1 2
  2 3
  3 5
  5 6
  6 2
  2 7
  7 8
  8 3
  3 4

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

例题 6-16  

Play On Worlds (UVa 10129)

题目:输入 n 个单词,判断是否可以把所有这些单词排成一个序列,使得每个单词的第一个字母和上一个单词的最后一个字母相同(例如 acm、malform、mouse)。

 

基本思路:

  将字母看成点,每个单词看成有向的边,从首字母指向尾字母,构成一个有向图。题目就变成了,是否能每个边都只走一次,就遍历完所有的边,即变成了欧拉路径的问题。

  要构造欧拉路径,当且仅当两个条件都满足:

    ① 底图(忽略边方向后得到的无向图)是连通的

    ② 所有点的入度等于出度,或者仅存在两个点,其中一个点出度比入度大一,另一个点入度比出度大一,其他的点出度等于入度。

  条件 ① 可以用 dfs 来解决。一个图是连通的当且仅当图中只存在一个连通块。

  条件 ② 可以循环遍历 26 个结点来判断。(有的结点不在图中,但 出度=入度=0,因此没有影响)。

AC代码:

  

/* Play in Words (UVa 10129) */
/* 欧拉路径 */ 
#include <iostream>
#include <cstring>
#include <string>
using namespace std;

const int MAX = 26;

int in[MAX], out[MAX];                //每一个点的出度和入度
int G[MAX][MAX], vis[MAX][MAX];        //两点之间是否存在边,以及这条边是否走过。

bool dfs(int u);                    //若存在连通块则返回 true ,否则返回 false
bool communication();                //检查图是否连通
bool check_in_out();                //通过出入度数判断是否可以构成欧拉路径

int main(){
    //freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);
    int T, N;
    cin >> T;
    
    while(T--){
        bool ok = true;        //是否能构成欧拉路径 
        memset(vis, 0, sizeof(vis));
        memset(G, 0, sizeof(G));
        memset(in, 0, sizeof(in));
        memset(out, 0, sizeof(out));
        
        cin >> N;
        while(N--){
            string s;
            cin >> s;
            int begin=s[0]-'a', end=s[s.length()-1]-'a';            //单词为边,从首字母出发,到尾字母
            /*if(begin == end)        //若单词的首尾字母相同,则可以忽略这个单词 
                continue;*/
            in[end]++;         //到尾字母 
            out[begin]++;        //从首字母出发 
            G[begin][end] = G[end][begin] = 1;    //构成底图(无向图) 
        }
        
        if(communication()){        //如果图是连通的,就再检查出入度数是否满足要求
            if(!check_in_out())
                ok = false;
        }else{
            ok = false;
        }
        
        if(ok)
            cout << "Ordering is possible." << endl;
        else
            cout << "The door cannot be opened." << endl;
    }
    return 0;
}

bool communication(){
    //如果图是连通的,则图中当且仅能存在一个连通块
    bool flag = false;    //是否已经找到一个连通块 
    for(int u=0; u<MAX; u++){
        if(dfs(u)){                //如果找到了一个连通块 
            if(flag == true)        //并且已经存在一个连通块了,则图不是连通的
                return false;
            flag = true; 
        } 
    }
    return true;
}

bool dfs(int u){
    bool find = false;        //是否找到连通块 
    for(int v=0; v<MAX; v++){
        if(G[u][v] && !vis[u][v]){        //如果存在边,并且没有走过
            find = true;                //找到连通块了
            vis[u][v] = vis[v][u] = 1;    //标记已经走过这条边
            dfs(v); 
        } 
    }
    return find;
} 

bool check_in_out(){
    bool in_node = true;        //存在一个入度比出度大一的点(最多只能存在一个这样的点) 
    bool out_node = true;    //存在一个出度比入读大一的点(最多只能存在一个这样的点) 
    
    for(int i=0; i<MAX; i++){
        if(in[i] == out[i]){    //如果出度和入度相等,则检查下一个点 
            continue;
        }else if(in[i]-1 == out[i] && in_node){        //如果入度比出度大一,并且这个点存在 
            in_node = false;
            continue;
        }else if(out[i]-1 == in[i] && out_node){
            out_node = false;
            continue;
        }else{
            return false;
        }
    }
    
    if(in_node != out_node)        //如果两个点一个存在一个不存在,则不能构成欧拉路径
        return false;
    
    return true; 
}

 

  

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!