欧拉图的课题研究
欧拉图是指通过图(无向图或有向图)中所有边且每边仅通过一次通路,相应的回路称为欧拉回路。具有欧拉回路的图称为欧拉图(Euler Graph),具有欧拉通路而无欧拉回路的图称为半欧拉图。
其实分解下来定义精华:
欧拉图听名字就知道发明创作者是谁,七桥问题其实是欧拉提出的论文中图论的第一篇论文“哥尼斯堡七桥问题”。
在当时的哥尼斯堡城有一条横贯全市的普雷格尔河,河中的两个岛与两岸用七座桥连结起来。当时那里的居民热衷于一个难题:有游人怎样不重复地走遍七桥,最后回到出发点。(其实故事挺迷的,大家都好闲)
为了解决这个问题,欧拉用 A,B,C,D 4个字母代替陆地,作为 4 个顶点,将联结两块陆地的桥用相应的线段表示,于是哥尼斯堡七桥问题就变成了图中,是否存在经过每条边一次且仅一次,经过所有的顶点的回路问题了。欧拉在论文中指出,这样的回路是不存在的。
然后巨佬就给出了专业的解析过程。
欧拉把每一块陆地考虑成一个点,连接两块陆地的桥以线表示。
后来推论出此种走法是不可能的。
他的论点是这样的,除了起点以外,每一次当一个人由一座桥进入一块陆地(或点)时,他(或她)同时也由另一座桥离开此点。所以每行经一点时,计算两座桥(或线),从起点离开的线与最后回到始点的线亦计算两座桥,因此每一个陆地与其他陆地连接的桥数必为偶数。
手推此处
在使用欧拉图的时候:
我们经常需要判定一个图是否为欧拉图(或半欧拉图),并且找出一条欧拉回路(或欧 拉路径)。
判定的算法思路(这部分如果题目已经可以明确确定是欧拉图,则不用编写):
在此因主题原因,故不再讨论半欧拉图,只考虑欧拉图。
之前欧拉对于七桥问题的解决思路很好的给我们判断欧拉图是否成立,一些思路。
还是有严谨证明思路与定理来支撑我们判断欧拉图。
欧拉图的定理与性质(常用部分):
**这一部分我是从巨佬的博客下引用过来的,精简了一下
在此声明非本人原创,传送门:大佬“ DZYO” 的博客
另外,证明我是真的没有完全看懂。
一、
无向图G为欧拉图,当且仅当G为连通图且所有顶点的度为偶数。
二、
三、
要想一个图G是欧拉图,图G需要满足两个条件:
针对有向图来说:
1.图G是连通的,不能有孤立的点存在。
2.每个顶点的入度要等于出度。
(离开此点与到达此点的边数相同。)
针对无向图来说:
1.图G是连通的,不能有孤立的点存在。
2.度数为奇数的点的个数为0。
无向图这里给了一个图解,大家可以看看。挺可爱的。有助理解,但它讲的方面不是很全,如果没有看明白也不用强迫着去看,忽略就好。
要推出几种算法思路,我们需要明确欧拉图的成立条件(上文已简单叙述)
如果只是仅仅需要运用欧拉回路,可直接运用下列算法。
欧拉图的算法都有一些缺陷,也有一些优点,请论情况使用。
由此可以推出欧拉图的算法有几种:
dfs&&查并集
dfs算法思想
利用欧拉定理判断出一个图存在欧拉通路或回路后。
选择一个正确的起始顶点,用DFS 算法遍历所有的边(每条边只遍历一次),遇到走不通就回退。在搜索前进方向上将遍历过的边按顺序记录下来。这组边的排列就组成了一条欧拉通路或回路。
这个很简单但挺实用,大家应该对于dfs都很熟悉了,毕竟已经学了图论,不多赘述。
(弗勒里)算法
重点!!!
说句实话,算法就是加上割边判定的深搜。
虽然复杂度较差,但实现简单。(所以有什么用)
这个算法是较为高效地运用了图论思想,也是求解欧拉回路最有名且使用广泛的算法之一。
这个算法是弗勒里(B.H.) 在1883 年给出了在欧拉图中找出一个欧拉环游的多项式时间算法。
运用前我们需要明确“桥”(割边)的含义。
假设有连通图G,e是其中一条边,如果G-e是不连通的,则边e是图G的一条割边。
然后看算法。
其算法核心就是沿着一条迹往下寻找,先选择非割边,除非这个点的邻边都是割边。这样得到一条新的迹,然后再继续往下寻找,直到把所有边找完。遵循这样一个原则就可以找出图的一个欧拉回路来。
我下面虽然有实现模板,但我把重点截出来一下。
核心代码挺简单的,主要是对于割边的判断很重要。
查并集+递归
并查集一般用来确定图是否连通,然后根据度来判断是否存在欧拉图,递归用来输出路路径。
但递归打印路径时候需要注意:必须先确定里面有欧拉路径,欧拉回路直接打印即可,欧拉通路需要找到起点。
有部分题需要用到这一种算法,但是实现真的挺复杂的。
另外,这玩意儿容易爆栈。
毕竟递归次数有点多。
所以防止爆栈的话用非递归。
这个方法挺迷,参考大佬的博客:dalao传送门
这一部分仅供参考,最好自己实现算法,毕竟欧拉图深搜挺简单的。
选了可读性稍微好一点的模板,主要大部分博客上的代码写得真的很乱,可读性差。前两个模板来自楼上提到的大佬。
1、查并集判断连通图
#include
#include
#define N 1000using namespace std;int n, m;
int f[N],degree[N];//记录第i点的度数void init()
{for (int i = 1; i <= n; i++)f[i] = i;
}
int find(int x)
{return x == f[x] ? x : f[x] = find(f[x]);
}
void merge(int x, int y)
{int t1, t2;t1 = find(x); t2 = find(y);if (t1 != t2) f[t2] = t1;else return;
}
int isEuler()
{int cnt=0;for (int i = 1; i <= n; i++){if (degree[i] & 1) cnt++;if(cnt>2) return 0;}if(cnt==2||cnt==0) return 1;///cnt==2 欧拉通路 cnt==0欧拉回路return 0;
}
int isconnect()
{int cnt = 0;int ff=find_(1);for (int i=2; i <= n; i++){if (find_(i)!=ff)return 0;}return 1;
}
int main()
{while (scanf("%d", &n) != EOF && n){init();memset(degree, 0, sizeof(degree));scanf("%d", &m);int t1, t2;for (int i = 0; i < m; i++){scanf("%d%d", &t1, &t2);//输入有t1,t2相等的情况if (t1 == t2)continue;degree[t1]++; degree[t2]++;merge(t1, t2);}printf("%d\n", isEuler() && isconnect());}return 0;
}
2、输出路径
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
#define PI 3.1415926535897932384626
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=6000;
struct Edge
{int to,next,id;bool flag;
}edge[maxn];
int head[maxn];
int in[maxn];
int tot;void addedge(int u,int v,int w)
{edge[tot].to=v;edge[tot].next=head[u];edge[tot].id=w;edge[tot].flag=false;head[u]=tot++;
}
int start;
vector<int>ans;
void init()
{tot=0;memset(head,-1,sizeof(head));memset(in,0,sizeof(in));
}
void dfs(int x)
{for(int i=head[x];i!=-1;i=edge[i].next){if(!edge[i].flag){edge[i].flag=true;edge[i^1].flag=true;dfs(edge[i].to);ans.push_back(i);//记录边的号码}}
}
int main()
{int x,y,c;while(scanf("%d%d",&x,&y)){if(x==0&&y==0)break;init();scanf("%d",&c);addedge(x,y,c);addedge(y,x,c);in[x]++;in[y]++;start=min(x,y);while(scanf("%d%d",&x,&y)){if(x==0&&y==0)break;scanf("%d",&c);addedge(x,y,c);addedge(y,x,c);in[x]++;in[y]++;}int flag=1;for(int i=1;i<=maxn;i++) //判断是否为欧拉回路{if(in[i]%2!=0){flag=0;break;}}if(flag==0){puts("Round trip does not exist.");continue;}ans.clear();dfs(start);for(int i=0;i<ans.size();i++){if(i==0)printf("%d",edge[ans[i]].id);elseprintf(" %d",edge[ans[i]].id);}cout<<endl;}return 0;
}
3、算法实现
//欧拉路径的输出(此图为无向图)
#include
using namespace std;
#define M 200
struct stack
{int top,node[M];
}s; //顶点的栈结构
int Edge[M][M]; //邻接矩阵
int n; //顶点个数void dfs(int x) //这里的深度优先跟标准版有所区别,即不需要回溯
{s.top++;s.node[s.top]=x; //将即将要扩展的结点压入栈中for(int i=0;i<n;i++){if(Edge[i][x]) //如果该节点还存在边{Edge[i][x]=0; Edge[x][i]=0; //删除该边,然后搜索另一结点dfs(i);break;}}
}void Fleury(int x) //对头节点使用Fleury算法 查找欧拉路径
{s.top=0;s.node[s.top]=x;while(s.top>=0){int flag=0; //记录当前结点是否有边可以扩展for(int i=0;i<n;i++){if(Edge[i][s.top]){flag=1;break;}}if(!flag){cout<<s.node[s.top]+1<<" "; //记录时是从0--n-1,所以应该加1s.top--; //结点输出了,此结点出栈}else{s.top--; //因为dfs处理时,需要先进栈,所以这里要先出栈,然后再进栈dfs(s.node[s.top+1]); //处理那个结点}}cout<<endl;
}int main()
{int m,s,t; //边数,读入的边的起点和终点int degree,num,start; //每个顶点的度、基度顶点个数、欧拉回路的起点cin>>n>>m; //n---顶点数 m---边数memset(Edge,0,sizeof(Edge));for(int i=0;i<n;i++){cin>>s>>t;Edge[s-1][t-1]=1;Edge[t-1][s-1]=1;}num=0;start=0; //如果存在奇度顶点,则从奇度顶点出发,否则从顶点0出发for(int i=0;i<n;i++){degree=0;for(int j=0;j<n;j++)degree+=Edge[i][j];if(degree%2){num++;start=i;}}if(num==0||num==2) Fleury(start);else cout<<"No Euler path"<<endl;return 0;
}
难度排序:
模板题:
poj:1041 john’s trip
(无向图欧拉回路&路径输出,推荐必须要做的模板题,很经典。)
hdu:3018 ant trip
题解
(并查集:欧拉回路的问题)
一般难度:
POJ:1386 Play on Words
(判定有向图图欧拉路径是否存在)
POJ 1300 Door Man
(无向图欧拉通路和回路的判定+并查集)
究极(难一点的)难度:
大视野oj:太鼓达人
这道题是无意中翻到的,完全没看出来是欧拉图。需要强大的数学功底才能进行转化(要用欧拉回路判定+dfs+回溯做)。
这里选了一个题解(思路讲的较为清晰,有图解)
太鼓达人题解
另外,其实是为了表明我是音游狂热蒟蒻的身份才把这道题加上的。
uva:10735
是道紫题,此处给的是洛谷传送门,自己想要在uva上测评的给了题号,(我没用VPN上uva太卡了,便没给链接)。
洛谷题解挺详细,大家可以看看。
poj:1637 tour
一道好题(大佬如是说)。
要用混合图欧拉图(上面的uva也是这种),但这道题还要用网络流。
另外顺手收集的题目,想要刷题时可以做做。
另外题解:
dzyo大佬传送门
最后,
很多方面都参考了这位大佬,大家可以看一看他的blog。(前面也有传送门)
感谢各位大佬的博客!!!
欧拉图学习强调的是图论思想而非算法实现,要不然它的实现为什么都这么水
大家注意理解图论的本质就好,实现很简单的。
另外如果有问题欢迎大佬们指出!!!
关于拓展的哈密顿路径问题
哈密顿路径问题与哈密顿回圈问题属于数学中的图论。此问题是用来决定一个哈密顿图上的路径或回圈。两个问题皆为NP完全。为旅行推销员问题的特殊案例。
主要是因为这个问题现在还没有解决,所以资料很少。所以暂时不做系统研究,感兴趣可以自己gg。
讲完后的最后一些想法和反馈 关于算法
我发现这是一个很神奇的东西,神奇到Wiki都没有提到过,欧拉图竟然是纯粹的图论情怀产物,一般遇不到,解决方法也很暴力,但主要重点在思维上。
我因为查阅资料的方式只有看别人的博客和Wiki,所以知识量有限,无法做到完全学术性精确。因此用这篇来简单讲一下。
欧拉图本身
其实对于欧拉图,我感觉它还是挺小众的,我经常做题看不懂题解什么的。而且大家都不怎么讲的清楚。反正做完感觉除了我做课题技术见长,代码实现能力还是很菜。
反正快乐过头。