欧拉路和欧拉圈,简言之就是,从无向图的一个结点出发,走一条路/圈,每条边恰好经过一次,即一笔画问题
欧拉定理:一个无向图最多只有两个奇结点,那么我们就从一个奇结点出发,到另一个结点为之,一定有一条欧拉路。
无向图:最多只能有两个奇节点的图则可判定为有欧拉路
有向图:最多只能有两个结点的入度和出度不相同,必须其中一个结点的入度比出度大1(终点),另一个结点的出度比入度大1(起点),且其无向图(即底图)是连通的。
应用:
- 判定欧拉路/圈的有无:根据底图的连通性+结点的度数判定,其中连通性的判定可使用DFS(见种子填充)或者并查集
- 如果有,构造欧拉路/圈:使用下面的代码:
1 //用DFS构造欧拉路/圈 2 void euler(int u){ 3 for(int v=0;v<n;v++) if(G[u][v]&&!vis[u][v]){ 4 vis[u][v]=vis[v][u]=1; 5 euler(v); 6 printf("打印边:%d %d",u,v); 7 } 8 }
UVa中有一个类似的欧拉路的题目,涉及了复杂图的欧拉路的判定(有复边)
大意如下:给定N个单词(1<=N<=100000)判断能否构成词语接龙,即后一个单词的首字母与前一个单词的首字母一致。一个单词每出现一次就要考虑一次。详细的题目描述见传送门
传送门:UVa 10129
思路如下:
- 根据要求,我们要判定给定的N个单词能否构成词语接龙,那么我们只需要关注每个单词的开头字母和结尾字母。因此我们可以将每个单词看作从首字母到尾字母的一条边,那么词语接龙就是要在所给的有向图中找到一条欧拉路或者一个欧拉圈。
- 判定有向图的条件见上方:1.底图连通。2.节点的度数满足上述条件。
- 用并查集来判定底图的连通性(dfs真是烦),p数组用于存储并查集的代表元
- p数组存储的是边的节点的代表元,当所有节点的代表元都一致,说明底图连通
- 入度和出度分别使用in和out两个数组进行记录,用G作为记录边是否出现的map,每条边只计算一次即可,减少并查集的判别量。
- 除了判定欧拉路,也可能存在欧拉圈,因此代码中也判定了欧拉圈
代码如下:
1 #include<cstdio> 2 #include<cstring> //memset()的定义 3 #include<iostream> 4 using namespace std; 5 6 #define MAX 26 7 8 int G[MAX][MAX],m;//G存储图,m存储边的数量,用于并查集 9 int u[MAX*MAX],v[MAX*MAX],p[MAX]; //边i 的端点为u,v,p[x]是用于并查集的标记 10 int in[MAX],out[MAX]; //记录每个端点的入度和出度 11 int t,n;//题目中的t和n 12 13 int char2int(char c){ 14 return c-'a'; 15 } 16 //Union Find Set 17 int find(int x){ 18 return p[x]==x?x:p[x]=find(p[x]); 19 } 20 //判断以单词为图的连通性 21 bool isConnect(){ 22 for(int i=0;i<MAX;i++)p[i]=i;//初始化并查集 23 for(int i=0;i<m;i++){ //按照边将端点的代表元进行合并 24 int x=find(u[i]),y=find(v[i]); 25 if(x!=y)p[x]=y;//Union 这样的Union使得连通性的判断实际上就是无向图的连通性的判断 26 } 27 int flag=find(u[0]);//将第一个边的u端点作为代表检测元 28 for(int i=1;i<m;i++)if(find(u[i])!=flag) return false;//如果出现了不同的代表元,则说明图不连通 29 return true; 30 } 31 bool hasStartEnd(){ 32 int start=-1,end=-1; 33 for(int i=0;i<MAX;i++){ 34 if(end<0 && in[i]==out[i]+1)end=i; 35 else if(start<0 && in[i]+1==out[i])start=i; 36 else if(in[i]!=out[i])return false;//其他点如果入度和出度不相同,则不可能有欧拉路或者欧拉圈 37 } 38 return start==-1&&end==-1 || start>=0&&end>=0;//两者同时为-1时是欧拉圈,同时大于等于0是欧拉路 39 } 40 bool hasEuler(){ 41 if(n==1)return true; //如果只有一个单词,那么肯定是可以得 42 return isConnect()&&hasStartEnd();//注意顺序不能反,从定义上不能反 43 } 44 int main(){ 45 string s; 46 scanf("%d",&t); 47 while(t--){ 48 scanf("%d",&n); 49 memset(G,0,sizeof(G));m=0;//初始化边的数目和图 50 memset(in,0,sizeof(in));//初始化入度出度数组 51 memset(out,0,sizeof(out)); 52 while(n--){ 53 cin>>s; 54 int u1=char2int(s[0]),v1=char2int(s[s.length()-1]);//顶点u和v 55 if(!G[u1][v1]){ 56 u[m]=u1,v[m]=v1;//将边m的端点记录下来,便于使用并查集 ,也相当于初始化了边的端点集合 57 G[u1][v1]=1;m++;//做判断是为了并查集的边数尽可能的小,所以用了if 58 } 59 in[v1]++,out[u1]++; 60 } 61 hasEuler()?puts("Ordering is possible."):puts("The door cannot be opened."); 62 } 63 }
今天就到这里啦~欧拉路和欧拉圈就搞定啦~see you~(●'◡'●)