欧拉图
本文思想借助大佬: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&°[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); }
以上;