歐拉路徑與歐拉回路
感覺這一塊網上說的有點亂,很多東西都沒有說清楚,或者都缺一些東西,所以在這裏打算好好的總結與歸納一下關於歐拉路徑與歐拉回路的問題。
概念
歐拉路徑:從某一起點開始,可以沿某路徑遍歷圖中每一條邊一次且僅一次,則稱此路徑爲歐拉路徑
歐拉回路:若歐拉路徑中的起點和終點相同,則其爲歐拉回路
一般情況下,如果一個圖是由歐拉回路構成的,我們則稱之爲歐拉圖。否則,當其是由歐拉路徑構成的話,我們就稱之爲半歐拉圖。
充要條件關係:
歐拉回路:所有點的滿足:入度 = 出度,那麼沿任意一點爲起點,一定存在方案形成歐拉回路。
歐拉路徑:有僅有一個起點:入度 = 出度 - 1,有且僅有一個終點:出度 = 入度 - 1,。其他的點都滿足:入度 = 出度。則我們從起點開始遍歷即可。
細心的讀者可能會發現,如果我們在歐拉路徑上畫一條由終點指向起點的路徑的話,那麼歐拉路徑就會變成歐拉回路,所以我們可以看出他們事實上很容易相互轉化的。
所以歐拉路徑與歐拉回路除了在判斷其充要條件時略有不用外,他們的求法是一樣的,所以我們這裏以歐拉路徑爲例。
無向圖&有向圖
Fleury + dfs
最簡單也是最基礎的做法。
這裏採用鄰接表存放圖,在優化中大家就會發現這樣有意想不到的好處。
@Frosero
#include <cstdio>
#include <cstring>
#include <stack>
#define MAXN 100010
using namespace std;
struct N{ //鄰接表存放圖會有意想不到的好處
int v,nex;
bool vis;
};
N edge[MAXN];
int fir[MAXN],tot;
void add_edge(int u,int v){
edge[tot].v = v;
edge[tot].vis = false;
edge[tot].nex = fir[u];
fir[u] = tot++;
}
stack<int>stk;
int ans[MAXN],cnt;
void dfs(int s){ //Fluery尋找增廣路
stk.push(s);
for(int i=fir[s];i!=-1;i=edge[i].nex)if(!edge[i].vis){
edge[i].vis = true;
dfs(edge[i].v);
break;
}
}
void Fluery(int s){ //Fluery主過程
while(!stk.empty()) stk.pop(); stk.push(s);
while(!stk.empty()){
int u = stk.top(); stk.pop();
bool flag = false;
for(int i=fir[u];i!=-1;i=edge[i].nex)if(!edge[i].vis){
flag = true; //已檢測到存在增廣路
break;
}
if(flag) dfs(u);
else ans[--cnt] = u; //注意要逆序存放,才能正序輸出
}
}
int n,m; //n爲點的數量,從1開始計數,m爲邊的數量
int is[MAXN],os[MAXN]; //is代表入度,os代表出度
int main(){
//freopen("in.in","r",stdin);
//freopen("out.txt","w",stdout);
while(scanf("%d %d",&n,&m)!=EOF){
memset(fir,-1,sizeof(fir));
memset(is,0,sizeof(is));
memset(os,0,sizeof(os));
tot = 0;
int u,v;
for(int i=0;i<m;i++){
scanf("%d %d",&u,&v);
add_edge(u,v);
os[u]++; is[v]++;
}
int s = 1,t = 1,s_cnt = 0,t_cnt = 0; //s爲起點,t爲終點
bool flag = true; //s_cnt爲適合做起點的點數,t_cnt爲適合做終點的點數
for(int i=1;i<=n;i++){
if(is[i] + 1 == os[i]){
s = i;
s_cnt++;
}
else if(is[i] == os[i] + 1){
t = i;
t_cnt++;
} //若一個點入度和出度相差2及以上,則不可能存在歐拉路徑
else if(is[i] != os[i]) flag = false;
} //歐拉路徑存在的充要條件
if(flag && ((s_cnt == 1 && t_cnt == 1) || (s_cnt + t_cnt == 0))){
cnt = m + 1; //m條邊則一定且只經歷m+1個點,否則也不是歐拉路徑
Fluery(s);
if(cnt != 0) printf("No Eulers !\n");
else{
for(int i=0;i<m+1;i++) printf("%d ",ans[i]); printf("\n");
}
}
else printf("No Eulers !\n");
}
return 0;
}
Fleury + bfs
然而上述算法適用性及其有限,其中一點就是:dfs過深導致爆棧
爲解決此問題,我們可以把dfs過程用bfs來寫。
代碼如下:
@Frosero
#include <cstdio>
#include <cstring>
#include <stack>
#define MAXN 100010
using namespace std;
struct N{ //鄰接表存放圖會有意想不到的好處
int v,nex;
bool vis;
};
N edge[MAXN];
int fir[MAXN],tot;
void add_edge(int u,int v){
edge[tot].v = v;
edge[tot].vis = false;
edge[tot].nex = fir[u];
fir[u] = tot++;
}
stack<int>stk;
int ans[MAXN],cnt;
void bfs(int s){ //Fluery尋找增廣路,注意這裏改成了bfs
stk.push(s);
while(true){
bool flag = false;
for(int i=fir[s];i!=-1;i=edge[i].nex)if(!edge[i].vis){
edge[i].vis = true;
flag = true;
s = edge[i].v;
stk.push(s);
break;
}
if(!flag) break;
}
}
void Fluery(int s){ //Fluery主過程
while(!stk.empty()) stk.pop(); stk.push(s);
while(!stk.empty()){
int u = stk.top(); stk.pop();
bool flag = false;
for(int i=fir[u];i!=-1;i=edge[i].nex)if(!edge[i].vis){
flag = true; //已檢測到存在增廣路
break;
}
if(flag) bfs(u);
else ans[--cnt] = u; //注意要逆序存放,才能正序輸出
}
}
int n,m;
int is[MAXN],os[MAXN]; //is代表入度,os代表出度
int main(){
//freopen("in.in","r",stdin);
//freopen("out.txt","w",stdout);
while(scanf("%d %d",&n,&m)!=EOF){
memset(fir,-1,sizeof(fir));
memset(is,0,sizeof(is));
memset(os,0,sizeof(os));
tot = 0;
int u,v;
for(int i=0;i<m;i++){
scanf("%d %d",&u,&v);
add_edge(u,v);
os[u]++; is[v]++;
}
int s = 1,t = 1,s_cnt = 0,t_cnt = 0; //s爲起點,t爲終點
bool flag = true; //s_cnt爲適合做起點的點數,t_cnt爲適合做終點的點數
for(int i=1;i<=n;i++){
if(is[i] + 1 == os[i]){
s = i;
s_cnt++;
}
else if(is[i] == os[i] + 1){
t = i;
t_cnt++;
} //若一個點入度和出度相差2及以上,則不可能存在歐拉路徑
else if(is[i] != os[i]) flag = false;
} //歐拉路徑存在的充要條件
if(flag && ((s_cnt == 1 && t_cnt == 1) || (s_cnt + t_cnt == 0))){
cnt = m + 1; //m條邊則一定且只經歷m+1個點,否則也不是歐拉路徑
Fluery(s);
if(cnt != 0) printf("No Eulers !\n");
else{
for(int i=0;i<m+1;i++) printf("%d ",ans[i]); printf("\n");
}
}
else printf("No Eulers !\n");
}
return 0;
}
優化
然而自習分析算法複雜度可得知,時間複雜度是爲
在Fluery的主過程中,我們在不斷尋找是否存在增廣路時重複搜索已標記vis = true 的邊,因此我們只要利用鄰接表的“刪除邊”的功能,就能把其複雜度降爲
代碼如下:
@Frosero
#include <cstdio>
#include <cstring>
#include <stack>
#define MAXN 100010
using namespace std;
struct N{ //鄰接表存放圖會有意想不到的好處
int v,nex; //注意這裏不再使用vis來標記
};
N edge[MAXN];
int fir[MAXN],tot;
void add_edge(int u,int v){
edge[tot].v = v;
edge[tot].nex = fir[u];
fir[u] = tot++;
}
stack<int>stk;
int ans[MAXN],cnt;
void bfs(int s){ //Fluery尋找增廣路,注意我們改的還是這裏 ^*_*^....嘿嘿
stk.push(s);
while(true){
bool flag = false;
int las = -2;
for(int i=fir[s];i!=-1;i=edge[i].nex){
flag = true;
if(las == -2) fir[s] = edge[i].nex;
else edge[s].nex = edge[i].nex;
s = edge[i].v;
stk.push(s);
las = i;
break;
}
if(!flag) break;
}
}
void Fluery(int s){ //Fluery主過程
while(!stk.empty()) stk.pop(); stk.push(s);
while(!stk.empty()){
int u = stk.top(); stk.pop();
bool flag = false;
for(int i=fir[u];i!=-1;i=edge[i].nex){
flag = true; //已檢測到存在增廣路
break;
}
if(flag) bfs(u);
else ans[--cnt] = u; //注意要逆序存放,才能正序輸出
}
}
int n,m;
int is[MAXN],os[MAXN]; //is代表入度,os代表出度
int main(){
//freopen("in.in","r",stdin);
//freopen("out.txt","w",stdout);
while(scanf("%d %d",&n,&m)!=EOF){
memset(fir,-1,sizeof(fir));
memset(is,0,sizeof(is));
memset(os,0,sizeof(os));
tot = 0;
int u,v;
for(int i=0;i<m;i++){
scanf("%d %d",&u,&v);
add_edge(u,v);
os[u]++; is[v]++;
}
int s = 1,t = 1,s_cnt = 0,t_cnt = 0; //s爲起點,t爲終點
bool flag = true; //s_cnt爲適合做起點的點數,t_cnt爲適合做終點的點數
for(int i=1;i<=n;i++){
if(is[i] + 1 == os[i]){
s = i;
s_cnt++;
}
else if(is[i] == os[i] + 1){
t = i;
t_cnt++;
} //若一個點入度和出度相差2及以上,則不可能存在歐拉路徑
else if(is[i] != os[i]) flag = false;
} //歐拉路徑存在的充要條件
if(flag && ((s_cnt == 1 && t_cnt == 1) || (s_cnt + t_cnt == 0))){
cnt = m + 1; //m條邊則一定且只經歷m+1個點,否則也不是歐拉路徑
Fluery(s);
if(cnt != 0) printf("No Eulers !\n");
else{
for(int i=0;i<m+1;i++) printf("%d ",ans[i]); printf("\n");
}
}
else printf("No Eulers !\n");
}
return 0;
}
到此,歐拉路徑及歐拉回路知識基礎就學好了,可以進軍更復雜的混合圖等啦~~