知其然
第一次接触欧拉回路是在离散数学这本书上,今天再次复习,便写下此博客。
欧拉回路是数学家欧拉在研究著名的德国哥尼斯堡(Koenigsberg)七桥问题时发现的。如图a所示,流经哥尼斯堡的普雷格尔河中有两个岛,两个岛与两岸共4处陆地通过7座杨 彼此相联。7桥问题就是如何能从任一处陆地出发,经过且经过每个桥一次后回到原出发点。
这个问题可抽象为一个如图b所示的数学意义上的图,其中4个结点分别表示与4块陆土Il 对应,如结点C对应河岸C,结点A对应岛A等,而结点之间的边表示7座桥。
——转载自百度百科
在说欧拉回路的时候需要先说一下其前置欧拉路径。
所谓欧拉路径就是一条能够走到底的路径(我自己的描述),就是说我们曾经玩过的一笔画问题。
而如果说上面的一笔画的起点和其终点相同的话,就称为欧拉回路。
现在既然知道了欧拉回路的概念,下面我们重新回到刚才说的哥尼斯堡七桥问题,对于这个问题我们现在如果不知道这个图是不是欧拉回路,因为数据范围特别小,所以我想每个人都会不自觉地脑补路径看看能不能进行“一笔画”,如果能的话就说明这个是欧拉回路,反之则不是,那么如果说数据范围很大的话,又该怎么办呢?
通常的求解方法有两种:DFS搜索和Fleury算法.
在了解这两个方法之前,需要掌握一些定理:
1:无向图G存在欧拉通路的充要条件如下:G是一个连通图并且G仅有两个奇度顶点或者没有奇度顶点.
推论:
1)当G时仅仅有两个奇度顶点的连通图的时候,G的欧拉通路一定是以这两个节点为端点。
2)当G是无奇度顶点的连通图的时候,G一定有欧拉回路。
3)G存在欧拉回路(是欧拉图)的充分必要条件是G是没有奇度顶点的连通图。
2:有向图G存在欧拉通路的充要条件如下:G的基图联通,并且所有顶点的出度与入度相等或者除两个顶点外其余顶点的出度等于入度,对于这两个顶点,一个顶点的出度-入度=1,另一个顶点的出度-入度=-1.
推论:
1)当D除了出、入度只差为1,-1的两个顶点外,其它顶点的出、入度相等时,D的有向欧拉通路必定以出入度只差为1的点作为起始点,另一个点作为终点。
2)当D的所有顶点的出、入度相等的时候,D中存在欧拉回路
忽略有向图所有边的方向,得到的无向图称为该有向图的基图
3:无向图是欧拉图的时候,只需要一笔就能画出来,如果说无向图图不是欧拉图的时候,需要x/2笔才能画出来(x是度为奇数的个数).
4:我们可以通过加边的方法使得一个非欧拉图转变成欧拉图,对于无向图来说,每个奇度顶点都需要加一个度,加边数量为奇点数/2,对于有向图来说,每个点都需要加上入度与出度只差,所加边数为所有顶点入度与出度只差的绝对值之和除以2.
1:DFS搜索
用DFS 搜索思想求解欧拉回路的思路为:利用欧拉定理判断出一个图存在欧拉通路或回路后,选择一个正确的起始顶点,用DFS 算法遍历所有的边(每条边只遍历一次),遇到走不通就回退。在搜索前进方向上将遍历过的边按顺序记录下来。这组边的排列就组成了一条欧拉通路或回路。
2:Fleury算法
设G 为一无向欧拉图,求G 中一条欧拉回路的算法为:
1) 任取G 中一顶点v0,令P0 = v0;
2) 假设沿Pi = v0e1v1e2v2 …eivi 走到顶点vi,按下面方法从E(G) - { e1, e2, …, ei }中选ei+1:
a) ei+1 与vi 相关联;
b) 除非无别的边可供选择,否则ei+1 不应该是Gi = G - { e1, e2, …, ei }中的桥。
3) 当2)不能再进行时算法停止。
可以证明的是,当算法停止时,所得到的简单回路Pm = v0e1v1e2v2 …emvm, (vm = v0)为G 中一条
欧拉回路。
如果感觉上面的一大堆字看起来很麻烦,我就用图例进行一个说明。
除非万不得已,否则不走割边。
首先选择A作为起始点,找到与A关联的边,这个时候发现只有e1和e2,e1和e2都不是割边,那么我们现在可以取e1或者e3,这里我们选择e1(选择之后就假设这个边不存在了),然后到达B,与B关联的边是e2,e3,e4,其中e3是一个割边,那么只能选择e2和e4里面的一个,我们选择e2,这个时候我们到达了C,而与C关联的只有e4了,虽然e4是一个割边,但是我们现在也要选择e4,后面的选择e3也是这样,最后到达A.
/*
我们先结合定理进行分析:
1:如果是求有没有欧拉回路:
判断是有向图还是无向图,若为有向图,所有顶点出、入度相等
若为无向图,则没有奇度顶点.
2:如果是求有没有欧拉通路:
判断是有向图还是无向图,若为有向图,所有顶点的出度与入度相等或者除两个顶点外其余顶点的出度等于入度
若为无向图,仅有两个奇度顶点或者没有奇度顶点
这里以无向图为例,求是否存在欧拉回路或者欧拉通路
*/
结合POJ2337分析有向图部分
题意:
单词首尾相接,前一个单词最后一个字母和后一个单词第一个字母相同,问是否可以把所有单词串成一串,如果可以就打印出来(输出字典序最小的),反之输出"***"。
分析:
单词首尾相接,并且两个相邻单词尾首单词相同,那么我们就可以想象成一个图,就比如下面一个例子:
可以看做A...B和B....C.(如果没有想明白再思考一下再往下走.)
要输出字典序最小的解,首先需要按照字典序进行排序。然后建图,每一个单词相当于一条边。
建图完毕后需要判断是否存在欧拉通路.
然后判断是否是联通图,最后输出单词.
代码:
#include<algorithm>
#include<map>
#include<iostream>
#include<queue>
#include<vector>
#include<set>
#include<string>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=1e3+10;
const int inf=0x3f3f3f3f;
struct Edge{
int to,next;
int indexx;
bool flag;
}edge[maxn];
int head[maxn],tot;
void init(){
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w){
edge[tot].to=v;
edge[tot].next=head[u];
edge[tot].indexx=w;
edge[tot].flag=false;
head[u]=tot++;
}
string num[maxn];
int in[maxn],out[maxn];
int cnt,ans[maxn];
void dfs(int start){
for(int i=head[start];i!=-1;i=edge[i].next){
if(edge[i].flag==false){
edge[i].flag=true;
dfs(edge[i].to);
ans[cnt++]=edge[i].indexx;
}
}
}
int main()
{
int T,n;
cin>>T;
while(T--){
cin>>n;
init();
for(int i=1;i<=n;++i){
cin>>num[i];
}
sort(num+1,num+1+n);
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
int start=inf;
for(int i=n;i>=1;--i){
int u=num[i][0]-'a';
int v=num[i][ num[i].length()-1 ] - 'a';
addedge(u,v,i);
in[v]++,out[u]++;
if(u<start) start=u;
}
int c1=0,c2=0;
for(int i=0;i<26;++i){
if(out[i]-in[i]==1){
++c1;
start=i;
}
else if(out[i]-in[i]==-1){
++c2;
}
else if(out[i]-in[i]!=0){
c2=3;
}
}
if( (c1==1&&c2==1)||(c1==0&&c2==0) ){
cnt=0;
// cout<<start<<endl;
dfs(start);
if(cnt!=n){
printf("***\n");
}
else{
for(int i=cnt-1;i>=0;--i){
if(i==0){
cout<<num[ ans[i] ];
}
else{
cout<<num[ ans[i] ]<<".";
}
}
cout<<endl;
}
}
else{
printf("***\n");
}
}
return 0;
}