欧拉图

时间:2021-10-02 21:45:48

本文思想借助大佬:https://www.cnblogs.com/wkfvawl/p/9626163.html

定义:

如果图G(有向图或无向图)中所有边一次仅且一次行遍左右顶点的通路称为欧拉通路;

如果图G中所有边一次且仅且一次行遍所有定点的回路称作欧拉回路;

具有欧拉回路的图称为欧拉图,具有欧拉通路但不具有欧拉回路的图称为版欧拉图;

判定:

判定欧拉通路是否存在的方法:

有向图:图联通,有一个顶点出度大入度1,有一个顶点入度大出度1,其余都是出度 == 入度;

无向图:图联通,只有两个顶点是奇数度,其余都是偶数度;

判定欧拉回路是否存在的方法:

有向图:图联通,所有定点的出度 == 入度;

无向图:图联通,所有定点都是偶数度;

求解方法:

A. DFS搜索求解欧拉回路;

基本思路:利用欧拉定理判断一个图存在欧拉回路或欧拉通路后,选择一个正确的起始顶点,有DFS算法变流所有边(每条边只遍历一次),遇到走不通就回退。在搜索前进方向上将遍历过得边按顺序记录下来。这组边的排列顺序就组成了一个欧拉通路或回路。

无向图:

注:以下代码为例题https://www.luogu.com.cn/problem/P1341答案,但同样可以作为模板:

#include<bits/stdc  .h>
using namespace std;
int n,head;
char a[2];
int b[130][130];
int deg[130],fa[130];
char ans[1330];
int find(int x)
{
    if(fa[x] == x) return x;
    return fa[x] = find(fa[x]); 
}
void dfs(int x)
{
    for(int i=64;i<=125;i  )
    {
        if(b[x][i])
        {
            b[x][i] = b[i][x] = 0;      
            dfs(i);
        }
    }
    ans[n--] = x;                           //由于是回溯储存点,则进行从后往前的方式储存点 
}
int main()
{
    cin>>n;
    for(int i=64;i<=125;i  )
    {
        fa[i] = i;    
    }        
    for(int i=1;i<=n;i  )
    {
        cin>>a;
        b[a[0]][a[1]] = b[a[1]][a[0]] = 1;  //将两点存在边进行标记 
        deg[a[0]]  ;                        //deg[]数组为记录每个顶点的度数 
        deg[a[1]]  ;            
        int xx = find(a[0]),yy = find(a[1]);
        fa[xx] = yy;                        //用并查集来进行是否是连通图的判断 
    }
    int cnt = 0;
    for(int i=64;i<=125;i  )
    {
        if(fa[i] == i&&deg[i])                     //对图进行计数,判断是否是仅有一个子块 
        {
            cnt  ;
        }
    }
    if(cnt!=1)
    {
        cout<<"No Solution"<<endl;          //如果cnt!=1,证明不是一个连通图,则输出错误; 
        return 0;
    }
    cnt = 0;
    head = 0;
    for(int i=64;i<=125;i  )
    {
        if(deg[i]&1)                        //对每个顶点度数进行判定,如果度数为奇数,则从cnt   
        {
            cnt  ;
            if(head ==0)head = i;
        }
    }
    if(cnt&&cnt!=2)                            //如果所有顶点中存在度数为奇数的点,但是数量不是两个,则证明不是欧拉通路; 
    {
        cout<<"No Solution"<<endl;
        return 0;
    }
    if(head == 0)                            //如果head==0,证明图中没有一个顶点的度数为奇数,则此图为欧拉回路; 
    {
        for(int i=64;i<=125;i  )
        {
            if(deg[i])
            {
                head = i;                    //由于是回路,则任意一个顶点都可以是开始的点 
                break;
            }
        }
    }
    dfs(head);                                 //对图进行dfs搜索 
    cout<<ans;
    return 0;
}

有向图:

有向图的判断步骤:

1.首先看有向图的基图是否连通。如果连通往下走,否则这一步就说明图不连通,欧拉通路不存在。判断图是否连通可以用并查集去判断。
如果最后求得所有节点在一个集合中,则图就是连通的。


2.统计每个节点的入度和出度。应该只有出度与入度相等,出度与入度差为1,出度与入度差为-1这三种节点。如果某个节点的出度与入度的关系不在这三个之内,则说明欧拉通路不存在。


3.

(1)如果发现出度与入度差为1和出度与入度差为-1的节点的个数都是0。即所有节点的出度与入度都相等,那么有向图中存在欧拉回路,当然也一定存在欧拉通路了。这时以任一节点开始深搜一条路径即可。
(2)如果发现存在一个出度与入度差为1的节点和一个出度与入度差为-1的节点,那么欧拉通路是以出度与入度差为1的节点为始点的,以出度与入度差为-1的节点为终点。
这时我们要以这个出度与入度差为1的节点为起点开始深搜。

节选大佬博客:https://www.2cto.com/px/201704/632179.html

最后附上代码:

#include<iostream>
#include<stdio.h>
#include
#include<string.h>
 
using namespace std;
 
const int maxn = 1002;
int N,CNT;
string word[maxn];                ///用来存放单词的数组
int in[30],out[30],ans[maxn];     ///in用来存放每个节点的入度,out用来存放每个节点的出度
bool mark[30];                    ///用来标记当前数据包含哪些小写字母
int father[30];                  ///并查集
struct Edge
{
    int u;                      ///起始节点
    int v;                      ///终端节点
    bool vis;                   ///用来标记该边是否被访问过
} edge[maxn];
void MAKE_SET()                 ///并查集初始化,每个节点的父母指向自己
{
    for(int i = 0; i < 26; i  )
        father[i] = i;
}
int FIND_SET(int x)             ///查找父母
{
    while(x!=father[x])
    {
        x = father[x];
    }
    return x;
}
void UNION(int x,int y)         ///集合合并
{
    x = FIND_SET(x);
    y = FIND_SET(y);
    if(x != y)
    {
        father[x]=y;
    }
}
bool IsConnection()  ///判断图是否连通
{
    int num = 0;
    for(int i = 0; i <= 26; i  )
    {
        if(mark[i])
        {
            if(father[i]==i)
                num  ;
        }
    }
    if(num==1) return true; ///所有节点都在一个集合里面
    else return false;
}
void DFS(int start)  ///深搜
{
    for(int i = 0; i < N; i  )  ///N条边
    {
        if(!edge[i].vis && edge[i].u == start)
        {
            edge[i].vis = true;
            DFS(edge[i].v);
            ans[CNT  ] = i;
        }
    }
}
int main()
{
    int T,i;
    cin>>T;
    while(T--)
    {
        cin>>N;
        for(i = 0; i < N; i  )
        {
            cin>>word[i];
        }
        sort(word,word N);  ///对字符串进行排序
        memset(mark,false,sizeof(mark));
        memset(in,0,sizeof(in));
        memset(out,0,sizeof(out));
        MAKE_SET();
        for(i = 0; i < N; i  )
        {
            int e = word[i].length();
            edge[i].u = word[i][0]-a;
            edge[i].v = word[i][e-1]-a;
            edge[i].vis = false;                        ///代表该边没有被选中过
            mark[edge[i].u] = mark[edge[i].v] = true;  ///代表26个字母中这些字母出现过
            in[edge[i].v]  ;        ///统计节点入度
            out[edge[i].u]  ;       ///统计节点出度
            UNION(edge[i].u,edge[i].v);  ///并查集操作,为后续判图是否连通做准备
        }
        int num1=0,num2=0,start=edge[0].u; 
        bool flag = true;
        for(i = 0; i < 26; i  )
        {
            if(mark[i])
            {
                if(in[i]==out[i]) continue;
                else if(out[i]-in[i]==1)
                {
                    num1  ;
                    start = i;
                }
                else if(out[i]-in[i]==-1)
                {
                    num2  ;
                }
                else
                {
                    flag = false;
                    break;
                }
            }
        }
        ///所有节点入度等于出度,或一个点出度与入度差为1,另一个点出度与入度差为-1
        if(flag && IsConnection() && ((num1==num2&&num1==0)||(num1==num2&&num1==1)))
        {
            CNT = 0;
            DFS(start);
}

以上;