歐拉回路基本概念+判斷+求解
1.定義
如果圖\(G\)(有向圖或者無向圖)中所有邊一次僅且一次行遍所有頂點的通路稱作歐拉通路。
如果圖\(G\)中所有邊一次僅且一次行遍所有頂點的回路稱作歐拉回路。
具有歐拉回路的圖成為歐拉圖(簡稱\(E\)圖)。具有歐拉通路但不具有歐拉回路的圖成為半歐拉圖。
頂點可以經過多次
畫個圖分辨一下:
- 歐拉通路:

- 歐拉回路:

簡單來講就是歐拉回路能夠回到起點
2.定理及推論
歐拉通路和歐拉回路的判定是很簡單的
無向圖\(G\)存在歐拉通路的充要條件是:
\(G\)為連通圖,並且\(G\)僅有兩個奇度節點(度數為奇數的頂點)或者無奇度節點
推論1:
- 當\(G\)是僅有兩個奇度節點的連通圖時,\(G\)的歐拉通路必以此兩個節點為端點
- 當\(G\)是無奇度節點的連通圖時,\(G\)必有歐拉回路
- \(G\)為歐拉圖(存在歐拉回路)的充分必要條件是\(G\)為無奇度節點的連通圖
有向圖\(D\)存在歐拉通路的充要條件是:
\(D\)為有向圖,\(D\)的基圖聯通,並且所有頂點的出度與入度都相等;或者除兩個頂點外,其余頂點的出度與入度都相等,而在這兩個頂點中一個頂點的出度與入度只差為\(1\),另一個頂點的出度與入度之差為\(-1\)
推論2:
- 當\(D\)除出、入度之差為\(1\),\(-1\)的兩個頂點之外,其余頂點的出度與入度都相等時,\(D\)的有向歐拉通路必以出、入度之差為\(1\)的頂點作為始點,以出、入度之差為\(-1\)的頂點作為終點
- 當\(D\)的所有頂點的出、入度都相等時,\(D\)中存在有向歐拉回路
- 有向圖\(D\)為有向歐拉圖的充分必要條件是\(D\)的基圖為連通圖,並且所有的頂點的出、入度都相等
3.歐拉通路回路存在的判斷
根據定理和推論,我們可以很好的找到歐拉通路回路的判斷方法
A.判斷歐拉通路是否存在的方法
- 有向圖:圖連通,有一個頂點出度大於入度\(1\),有一個頂點入度大於出度\(1\),其余都是出度=入度
- 無向圖:圖聯通,只有兩個頂點是奇數度,其余都是偶數度
B.判斷歐拉回路是否存在的方法
- 有向圖:圖聯通,所有的頂點出度=入度
- 無向圖:圖聯通,所有的頂點都是偶數度
4.歐拉回路的應用
- 哥尼斯堡七橋問題
- 一筆畫問題
- 旋轉鼓輪的設計
5.歐拉回路的判斷
\(DFS\)
鄰接矩陣的時間復雜度為\(O(n^2)\)
鄰接表的時間復雜度為\(O(n+e)\)
如果,重邊太多的話,鄰接表會掛掉:)
原題卡這個,所以我們要寫鄰接矩陣
const int N=1005;
int n,m;
int in[N];
bool vis[N],G[N][N];
void dfs(int x){
vis[x]=1;
for(int i=1;i<=n;i++){
if(G[x][i]&&!vis[i]){
dfs(i);
}
}
}
int main(){
while(~scanf("%d",&n)&&n){
m=read();
memset(G,0,sizeof G);
memset(vis,0,sizeof vis);
memset(in,0,sizeof in);
for(int i=1,x,y;i<=m;i++){
x=read();y=read();
G[x][y]=G[y][x]=1;
in[x]++;in[y]++;
}
dfs(1);
bool flag=1;
for(int i=1;i<=n;i++){
if(!vis[i]){
flag=0;
break;
}
if(in[i]%2){
flag=0;
break;
}
}
if(flag)puts("1");
else puts("0");
}
}
並查集
const int N=1005;
int n,m;
int in[N],fa[N];
int find(int x){
return x==fa[x]?x:x=find(fa[x]);
}
void union_set(int x,int y){
x=find(x);y=find(y);
if(x!=y)fa[x]=y;
}
int main(){
while(~scanf("%d",&n)&&n){
m=read();
for(int i=1;i<=n;i++){
fa[i]=i;
in[i]=0;
}
for(int i=1,x,y;i<=m;i++){
x=read();y=read();
in[x]++,in[y]++;
union_set(x,y);
}
bool flag=1;int totrt=0;
for(int i=1;i<=n;i++){
if(in[i]%2){
flag=0;
break;
}
if(find(i)==i){
totrt++;
}
}
//只有一個根且沒有入度為奇數的點
if(flag&&totrt==1)puts("1");
else puts("0");
}
return 0;
}
6.歐拉回路的求解
板子題:騎馬修柵欄
題目保證有解。
A.DFS搜索求解歐拉回路
基本思路:利用歐拉定理判斷出一個圖存在歐拉回路或歐拉通路后,選擇一個正確的起始頂點,用DFS算法遍歷所有的邊(每條邊只遍歷一次),遇到走不通就回退。在搜索前進方向上將遍歷過的邊按順序記錄下來。這組邊的排列就組成了一條歐拉通路或回路。
const int N=2005;
int m,Min,Max;
int G[N][N],in[N];
int path[N],cnt;
void dfs(int x){
for(int i=Min;i<=Max;i++){
if(G[x][i]){
G[x][i]--;
G[i][x]--;
dfs(i);
}
}
path[++cnt]=x;
}
void print_path(){
for(int i=cnt;i>=1;i--){
printf("%d\n",path[i]);
}
}
int main(){
m=read();Min=505,Max=0;
for(int i=1,x,y;i<=m;i++){
x=read();y=read();
G[x][y]++;G[y][x]++;
in[x]++,in[y]++;
Max=max(Max,max(x,y));
Min=min(Min,min(x,y));
}
for(int i=Min;i<=Max;i++){
if(in[i]%2){
dfs(i);
print_path();
return 0;
}
}//歐拉通路
dfs(Min);//歐拉回路
print_path();
return 0;
}
B.Fleury(佛羅萊)算法
- Fleury算法是對DFS爆搜的一種改進,使用DFS漫不經心的隨意走效率是不高的,Fleury是一種有效的算法
求法:
設\(G\)為一無向歐拉圖,求\(G\)中一條歐拉回路的算法為:
- 任取\(G\)中一頂點\(v_0\),令\(P_0=v_0\)
- 假設沿\(P_i= v_0e_1v_1e_2...e_iv_i\)走到頂點\(v_i\),按下面方法從\(E(G)-{e_1,e_2,...,e_i}中選\)\(e_{i+1}\)
- \(e_{i+1}\)與\(v_i\)相關聯
- 除法無別的邊可供選擇,否則\(e_{i+1}\)不應該是\(G_i=G-{e_1,e_2,...,e_i}\)中的橋
- 當2.無法進行時算法停止
可以證明的是,當算法停止時,所得到的簡單回路\(P_m= v_0e_1v_1e_2...e_mv_m\)為\(G\)中一條歐拉回路
我個人感覺是把大連通塊分成了零散的幾個小連通塊然后分塊連接(?)
關鍵是能不走橋就不走橋,實在無路可走了才會去走橋
const int N=2005;
int n,m;
int top,sta[N];
bool G[N][N];
void dfs(int x){
sta[++top]=x;
for(int i=1;i<=n;i++){
if(G[x][i]>0){
G[x][i]=G[i][x]=0;
dfs(i);
break;
}
}
}
void Euler(int x){
bool brige;
top=1;
sta[top]=x;
while(top>=0){
brige=0;
for(int i=1;i<=n;i++){
if(G[sta[top]][i]>0){
brige=1;
break;
}
}
if(!brige){
printf("%d ",sta[top--]);
}
else {
top--;
dfs(sta[top+1]);
}
}
}
int main(){
n=read();m=read();
for(int i=1,x,y;i<=m;i++){
x=read();y=read();
G[x][y]=1;
G[y][x]=1;
}
int num=0,start=1;
for(int i=1;i<=n;i++){
int deg=0;
for(int j=1;j<=n;j++)
deg+=G[i][j];
if(deg%2){
start=i;
num++;
}
}
if(num==0||num==2)Euler(start);
else {
puts("No Euler path");
}
return 0;
}
To be continue……