图论-欧拉图
欧拉回路与欧拉通路的判定
有向图
如果一个有向图存在欧拉通路,那么必定有两个点,一个是入口,一个是出口。入口点入度一定比出度小1,出口的入度一定比出度大1,这个入口和出口不存在的入边和出边称为虚拟边,而对于其他节点,入度=出度。
如果一个有向图存在欧拉回路,那么这两个虚拟边一定是同一条实际存在的边,因此有任意的节点,入度等于出度。
无向图
如果一个无向图存在欧拉通路,同理也有一个入口,一个出口,入口和出口的度数均为偶数。其余点的度均为奇数。
如果一个无向图存在欧拉回路,任意点的度均为偶数。
关于欧拉图判定的问题
分析
对于一个无向连通图,求用几笔能画完?每一次起笔都从度为奇数的节点开始画起,画到任意另一个度为奇数的点结束,去掉已经画完的边,这个图将可能被分成几个连通分量,但是奇数度的点的个数不会变,每画一次就会消掉一对奇数点,因此答案为奇数度点的个数除以2。对于欧拉图我们要特判一下,如果是欧拉图答案应该是1而不是0。
对于非连通图,我们应该通过dfs或者bfs用这个方法求每一个连通分量的笔画数,之后相加即可。
DFS求欧拉路
我们通过DFS求欧拉路的过程如下:
DFS(i):
遍历与i联通的边(i,v):
删除边(i,v)
DFS(v)
把i插入到答案序列中
网上很多博客给出了这个算法,但是没有讲明白为什么要后插点,前插点为什么不行的问题。
关于为什么后插点,我们必须搞清楚DFS的目的是什么。我们可以知道,在DFS过程中第一个返回的DFS的节点i一定是第一个无路可走的点,第一个无路可走的点一定是出口,因此把出口加入到答案序列中;同理,第二个返回的DFS一定是第二个无路可走点,一定是和出口点相连的第二个点。最后一个无路可走的点一定是入口,因此答案序列一定是一条欧拉路。
如果前插节点,那么可能先走到出口点,如果现在就把出口点插入序列中,答案必定是不对的。之所以前插不行,是因为前插的目的不符合我们DFS过程的目的。
例题
#include <bits/stdc++.h>
using namespace std;
#define FR freopen("in.txt","r",stdin)
#define FW freopen("out1.txt","w",stdout)
int graph[130][130];
int pre[130];
int deg[130];
int find(int u)
{
return pre[u] == u ? u : pre[u] = find(pre[u]);
}
void unite(int u,int v)
{
pre[find(u)] = find(v);
}
vector<char> ans;
void dfs(int curr)
{
for(int i = 64;i<=125;i++)
{
if(graph[curr][i])
{
graph[curr][i] = graph[i][curr] = 0;
dfs(i);
}
}
ans.push_back(curr);
}
int main()
{
for(int i = 64;i<=125;i++)
pre[i] = i;
int n;
cin >> n;
while(n--)
{
string s;
cin >> s;
graph[s[0]][s[1]] = graph[s[1]][s[0]] = 1;
unite(s[0],s[1]);
deg[s[0]]++;
deg[s[1]]++;
}
// connection
int cnt = 0;
for(int i = 64;i<=125;i++)
{
if(pre[i] == i && deg[i] != 0)
cnt++;
}
if(cnt != 1)
{
cout << "No Solution";
return 0;
}
int start = 0;
int pstart = 0;
cnt = 0;
for(int i = 64;i<=125;i++)
{
if(deg[i] & 1)
{
if(!start) start = i;
cnt++;
}
if(deg[i] != 0 && !pstart) pstart = i;
}
if(cnt && cnt != 2)
{
cout << "No Solution";
return 0;
}
if(cnt == 0) start = pstart;
dfs(start);
copy(ans.rbegin(),ans.rend(),ostream_iterator<char>(cout,""));
return 0;
}