欧拉路径、欧拉回路、Hierholzer算法

欧拉路径

内容参考自维基百科https://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。
充分性:如果图中没有奇顶点,那么随便选一个点出发,连一个环C1。如果这个环就是原图,那么结束。如果不是,那么由于原图是连通的, C1 和原图的其它部分必然有公共顶点 s1。从这一点出发,在原图的剩余部分中重复上述步骤。由于原图是连通图,经过若干步后,全图被分为一些环。由于两个相连的环就是一个环,原来的图也就是一个欧拉环了。
如果图中有两个奇顶点 u 和 v,那么加多一条边将它们连上后得到一个无奇顶点的连通图。由上知这个图是一个环,因此去掉新加的边后成为一条路径,起点和终点是 u 和 v。证毕。

下面介绍在无向图中寻找欧拉路径的 Hierholzer算法
例题:洛谷 P1341 无序字母对 https://www.luogu.org/problemnew/show/P1341
基本思想:
首先寻找图中所有度为奇数的点。
如果这样的点有0个或2个,那么可以存在欧拉回路,否则直接输出不能找到(这一步很重要,因为下面的dfs过程只能确保边只经过一次,却无法判断图中的边是否能被一笔画成,如果度为奇数的点的个数慢则条件,那么可以自动满足一笔画成。)
然后寻找欧拉路径的起点:如果有度为奇数的点(要么没有,要么有两个),那么找到两个点中ASCII码较小的那一个。如果没有度为奇数的点,那么就在所有点中找出ASCII码最小的点即可。
然后将每个节点可以联通的其他节点进行排序,确保在遍历时每个节点访问其下一个节点总是按照ASCII码从小到大的顺序。
最后在以上处理步骤的基础上进行dfs。dfs的方法:

void dfs(int p)
{
    for(int i = 0; i<to[p].size(); i++)
    {
        if(has_deleted[p][to[p][i]] == 0)
        {
            has_deleted[to[p][i]][p] = 1;
            has_deleted[p][to[p][i]] = 1;
            dfs(to[p][i]);
        }
    }
    ans[++k] = p;
}

代码的思想与树上dfs序有异曲同工之妙
这样插入的点一定是按照字典序从大到小的顺序(因为插入点是在一个节点的子节点全部被遍历完以后的操作)。输出时要倒序输出以保证字典序最小。

#include 
using namespace std;
int n;
char line[10];
vector<int> to[200];
int k,beginner;
int ans[10000];
int inf = 'z'+1;
int has_deleted[200][200];
void dfs(int p)
{
    for(int i = 0; i<to[p].size(); i++)
    {
        if(has_deleted[p][to[p][i]] == 0)
        {
            has_deleted[to[p][i]][p] = 1;
            has_deleted[p][to[p][i]] = 1;
            dfs(to[p][i]);
        }
    }
    ans[++k] = p;
}

int main()
{
    memset(has_deleted,0,sizeof(has_deleted));
    beginner = inf;
    k = 0;
    scanf("%d",&n);
    getchar();
    for(int i = 1; i<=n; i++)
    {
        scanf("%s",line);
        to[line[0]].push_back(line[1]);
        to[line[1]].push_back(line[0]);
    }
    int jj = 0;
    for(int i = 'A'; i<='Z'; i++)
    {
        if(int(to[i].size())%2 == 1)
        {
            jj++;
        }
    }
    for(int i = 'a'; i<='z'; i++)
    {
        if(int(to[i].size())%2 == 1)
        {
            jj++;
        }
    }
    if(jj == 0||jj == 2)
    {

    }
    else
    {
         printf("No Solution");
         return 0;
    }
    if(beginner == inf)
        for(int i = 'A'; i<='Z'; i++)
        {
            if(int(to[i].size())%2 == 1)
            {
                beginner = i;
                break;
            }
        }
    if(beginner == inf)
        for(int i = 'a'; i<='z'; i++)
        {
            if(int(to[i].size())%2 == 1)
            {
                beginner = i;
                break;
            }
        }
    if(beginner == inf)
        for(int i = 'A'; i<='Z'; i++)
        {
            if(int(to[i].size())>0)
            {
                beginner = i;
                break;
            }
        }
    if(beginner == inf)
        for(int i = 'a'; i<='z'; i++)
        {
            if(int(to[i].size())>0)
            {
                beginner = i;
                break;
            }
        }
    for(int i = 'a'; i<='z'; i++)
    {
        if(to[i].size()>0)
            sort(to[i].begin(),to[i].end());
    }
    for(int i = 'A'; i<='Z'; i++)
    {
        if(to[i].size()>0)
            sort(to[i].begin(),to[i].end());
    }
    k = 0;
    dfs(beginner);
    if(k == n+1)
    {
        for(int i = n+1; i>=1; i--)
        {
            printf("%c",ans[i]);
        }
    }
    else
    {
        printf("No Solution");
    }
}

你可能感兴趣的:(图论)