1. 程式人生 > 實用技巧 >【學習筆記】尤拉路Hierholzer演算法-UOJ117 歐拉回路

【學習筆記】尤拉路Hierholzer演算法-UOJ117 歐拉回路

(其實我不會念這個演算法的名字

題目連結

題目解析

我如果說我現在才會尤拉路還有救嗎

畢竟我關於尤拉路徑的題只做過這個-騎馬修柵欄,其他時候最多做到過判斷是否是尤拉路的題,並沒有輸出方案過

考試的時候腦子裡完全沒有蹦出來這四個字過,然後自己在那兒瞎寫(從零開始自己推這個演算法,不過我這麼菜當然是沒有成功,於是成功掛掉)

咳咳

網上好多部落格都將尤拉路徑和歐拉回路混為一談吶,但實際上是有區別的,尤拉路徑是一條經過圖中每一條邊恰好一次的路徑,而歐拉回路要求這條路徑是個迴路。顯然,歐拉回路的要求要高一些,兩者的判斷條件也有所不同。

判斷條件

  • 歐拉回路

    1. 無向圖:所有點的度數都是偶數。
    2. 有向圖:所有點出度等於入度。
  • 尤拉路徑

    1. 無向圖:相比歐拉回路,可以看成有一個起點,一個終點,即可以有兩個點的度數是奇數,開始搜尋時,起點為其中一個奇點(七橋問題
    2. 有向圖:同理,相比歐拉回路,可以看成有一個起點,一個終點,即可以有一個起點是出度=入度+1,一個終點是入度=出度+1
    3. 注意歐拉回路是尤拉路徑的一種特殊情況,即要注意考慮沒有奇點/所有點出入度都相同的情況。

演算法主體

從起點開始搜尋,走過一條邊時,先遞迴到下一個結點去,然後再把這條邊壓到棧中。

因為之前已經判斷過這個圖是否有尤拉路徑,所以可以大膽走,不要害怕出狀況,是一定可以繞圈圈繞回來的,而且挨個把鄰接邊全部搜完,是不會有圈圈搜不到的情況的。

可以想象一下,類比一下\(dfs\)

樹,其實差不多。

畫個圖吧:

一時手邊沒啥好用的畫圖軟體,將就看吧

所以一次\(dfs\)就可以完成了(真的很簡單,我為啥之前不會捏

(所以我看了網上好多部落格用了兩個棧一出一進的,搞得我很頭大

當前弧優化

現在的這個演算法其實還不夠優秀,因為雖然邊只會經過一次,但是點卻可以經過很多次,而每次回到這個點,你每次都會列舉到所有的邊,然後發現它不太對勁(已經列舉過),然後\(continue\)掉,而這些列舉都是無用的。

我們要避免這些無用列舉,所以可以像網路流那樣加個當前弧優化,就是記錄下來你現在枚到哪條邊了,下一次就不要列舉那些已經跑過的邊了。而我的做法就比較簡單粗暴了:因為每條邊只跑一次,所以把跑過的邊丟掉就好了。

我其實平時不咋用鏈式前向星的,一般用\(vector\),但是我不知道用\(vector\)怎麼寫當前弧優化(我嘗試過用倒序遍歷然後\(pop\_back\),但是我失敗了,而且是\(TLE\)(程式碼我放在後面了),估計是因為:它本來是在後面的搜尋中才回到當前節點時會遍歷掉一些邊,那麼回溯到之前的一次進入這個節點時,這些邊也不需要遍歷。而我的寫法只能在第二次進入這個節點時,節約掉列舉的那些邊,而我的第一次進去的時候就是從所有邊開始\(for\),這個地方並沒有優化到。

\(I\ suppose\ so,but\ I\ possess\ no\ evidence.\)

不過用前向星也挺好的(我網路流也是因為這個原因寫的前向星

不過在我寫完板子之後被安利了機房大佬的部落格,他就是用\(vector\)寫的:Lskr同學的blog

(不過我懶得改了


►Code View

#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define N 100005
#define M 200005
#define INF 0x3f3f3f3f
#define LL long long
int rd()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return f*x;
}
struct node{
	int v,nxt,w/*邊的編號*/;
}edge[M<<1];
int hd[N],cnt=1;//方便找反向邊(異或1 
void add(int u,int v,int w)
{
	edge[++cnt].v=v;
	edge[cnt].w=w;
	edge[cnt].nxt=hd[u];
	hd[u]=cnt; 
}

int T,m,n;
int fa[N],d[N],ind[N],st[N*10],tot;
void Init()
{
	for(int i=1;i<=n;i++)
		fa[i]=i;
}
int Find(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=Find(fa[x]);
}
void Union(int u,int v)
{
	u=Find(u),v=Find(v);
	if(u<v) fa[u]=v;
	else fa[v]=u;
}
void dfs(int u)
{
	//本來是:
	//for(int i=hd[u];i;i=edge[i].nxt)
	//但這裡加了當前弧優化 讓hd[]和i一起走 
	while(hd[u])
	{
		int i=hd[u];
		hd[u]=edge[i].nxt;
		if(edge[i].w)
		{//當前邊的反向邊沒有被選 
			int f=edge[i].w;
			edge[i].w=0;
			edge[i^1].w=0;//反向邊不能再選 
			//d[u]--;
			//d[edge[i].v]--;度數只用於判斷是否有解 後面沒有實際影響 
			dfs(edge[i].v);
			st[++tot]=f; 
		}
	}
	return ;
}
void solve1()
{
	for(int i=1;i<=m;i++)
	{
		int u=rd(),v=rd();
		add(u,v,i);
		add(v,u,-i);
		d[u]++,d[v]++;
		Union(u,v);
	}
	for(int i=1;i<=n;i++)
		if(d[i]&1)
		{
			puts("NO");
			return ;
		}
	int flag=0;
	for(int i=1;i<=n;i++)
		if(d[i])
		{//只要求每條邊經過一次 "孤島"沒有影響 
			if(!flag) flag=Find(i);
			else if(flag!=Find(i))
			{
				puts("NO");
				return ;
			}
		}
	int s=-1;
	for(int i=1;i<=n;i++)
		if(d[i])
		{
			s=i;
			break;
		}
	dfs(s);
	puts("YES"); 
	while(tot)
		printf("%d ",st[tot--]);
}
void dfs2(int u)
{
	while(hd[u])
	{
		int i=hd[u];
		hd[u]=edge[i].nxt;
		if(edge[i].w)
		{
			int f=edge[i].w;
			edge[i].w=0;
			dfs2(edge[i].v);
			st[++tot]=f;
		}
	}
	return ;
}
void solve2()
{
	for(int i=1;i<=m;i++)
	{
		int u=rd(),v=rd();
		add(u,v,i);
		d[u]++,ind[v]++;
		Union(u,v);
	}
	for(int i=1;i<=n;i++)
		if(d[i]!=ind[i])
		{
			puts("NO");
			return ;
		}
	int flag=0;
	for(int i=1;i<=n;i++)
		if(d[i]||ind[i])
		{
			if(!flag) flag=Find(i);
			else if(flag!=Find(i))
			{
				puts("NO");
				return ;
			}
		}
	int s=-1;
	for(int i=1;i<=n;i++)
		if(d[i]||ind[i])
		{
			s=i;
			break;
		}
	dfs2(s);
	puts("YES");
	while(tot)
		printf("%d ",st[tot--]);
}
int main()
{
	T=rd(),n=rd(),m=rd();
	Init();
	if(T==1) solve1();
	else solve2();
	return 0;
}


►Code View Ver.2

#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define N 100005
#define M 200005
#define INF 0x3f3f3f3f
#define LL long long
int rd()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return f*x;
}
vector<pair<int,int> >G[N];
void add(int u,int v,int w)
{
	G[u].push_back(make_pair(v,w));
}
int Abs(int x)
{
	if(x>=0) return x;
	return -x;
}
bool vis[M];

int T,m,n;
int fa[N],d[N],ind[N],st[N*10],tot;
void Init()
{
	for(int i=1;i<=n;i++)
		fa[i]=i;
}
int Find(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=Find(fa[x]);
}
void Union(int u,int v)
{
	u=Find(u),v=Find(v);
	if(u<v) fa[u]=v;
	else fa[v]=u;
}
void dfs(int u)
{
	for(int i=G[u].size()-1;i>=0;i--)
	{
		int v=G[u][i].first,w=G[u][i].second;
		if(vis[Abs(w)]) continue;
		vis[Abs(w)]=1;
		G[u].pop_back();
		dfs(v);
		st[++tot]=w;
	}
	return ;
}
void solve1()
{
	for(int i=1;i<=m;i++)
	{
		int u=rd(),v=rd();
		add(u,v,i);
		add(v,u,-i);
		d[u]++,d[v]++;
		Union(u,v);
	}
	for(int i=1;i<=n;i++)
		if(d[i]&1)
		{
			puts("NO");
			return ;
		}
	int flag=0;
	for(int i=1;i<=n;i++)
		if(d[i])
		{//只要求每條邊經過一次 "孤島"沒有影響 
			if(!flag) flag=Find(i);
			else if(flag!=Find(i))
			{
				puts("NO");
				return ;
			}
		}
	int s=-1;
	for(int i=1;i<=n;i++)
		if(d[i])
		{
			s=i;
			break;
		}
	dfs(s);
	puts("YES"); 
	while(tot)
		printf("%d ",st[tot--]);
}
void dfs2(int u)
{
	for(int i=G[u].size()-1;i>=0;i--)
	{
		int v=G[u][i].first,w=G[u][i].second;
		if(vis[w]) continue;
		vis[w]=1;
		G[u].pop_back();
		dfs(v);
		st[++tot]=w;
	}
	return ;
}
void solve2()
{
	for(int i=1;i<=m;i++)
	{
		int u=rd(),v=rd();
		add(u,v,i);
		d[u]++,ind[v]++;
		Union(u,v);
	}
	for(int i=1;i<=n;i++)
		if(d[i]!=ind[i])
		{
			puts("NO");
			return ;
		}
	int flag=0;
	for(int i=1;i<=n;i++)
		if(d[i]||ind[i])
		{
			if(!flag) flag=Find(i);
			else if(flag!=Find(i))
			{
				puts("NO");
				return ;
			}
		}
	int s=-1;
	for(int i=1;i<=n;i++)
		if(d[i]||ind[i])
		{
			s=i;
			break;
		}
	dfs2(s);
	puts("YES");
	while(tot)
		printf("%d ",st[tot--]);
}
int main()
{
	T=rd(),n=rd(),m=rd();
	Init();
	if(T==1) solve1();
	else solve2();
	return 0;
}