欧拉回路
一、引言
欧拉回路问题是图论中最古老的问题,诞生于18世纪欧洲的科尼斯堡问题。
二、定义
设\(G=(V,E)\)是一个图。
欧拉回路:图G中经过每条边一次并且仅一次的回路。
欧拉路径:图G中经过每条边一次并且仅一次的路径。
欧拉图:存在欧拉回路的图。
半欧拉图:存在欧拉路径但不存在欧拉回路的图。
三、性质与定理
假设下面讨论的图G不存在孤立点,否则,先将孤立点从图中删除。
对于无向图:
定理1:无向图G为欧拉图,当且仅当G为连通图且所有定点的度为偶数。
推论1:无向图G为半欧拉图,当且仅当G为连通图且除了两个顶点的度为奇数之外,其它所有顶点的度为偶数。
对于有向图:
定理2:有向图G为欧拉图,当且仅当G的基图连通,且所有顶点的入度等于出度。
推论2:有向图G为半欧拉图,当且仅当G的基图连通,且存在顶点u的入度比出度大1,v的入度比出度小1,其它所有顶点的入度等于出度。
基图:忽略有向图所有边的方向,得到的无向图成为该有向图的基图。
四、求欧拉回路
两种:Hierholzer(套圈)算法和Fluery算法,前者编程简单,时间复杂度更优,后者应用性更广泛,这里不深入分析。
Hierholzer算法:
#include<iostream>
#include<cstdio>
#include<set>
using namespace std;
const int N=1025;
multiset<int> h[N];
int degree[N], road[N], k;
void dfs(int x)
{
for(multiset<int>::iterator py=h[x].begin(); py!=h[x].end(); py=h[x].begin()){
int y=*py;
h[x].erase(py); //删边x->y
h[y].erase(h[y].find(x)); //删边x->y
dfs(y); //从y递归
}
road[k++]=x; //往队列里插入答案
}
int main(){
int F, s=0, t=0; //s起点,t终点
scanf("%d", &F);
for(int i=1, a, b; i<=F; i++) scanf("%d%d", &a, &b), degree[a]++, degree[b]++, h[a].insert(b), h[b].insert(a);
for(int i=1; i<=1024; i++) //注意模板题中栅栏结点的编号范围不是1~F
{
if(degree[i]%2){ //发现奇点
if(!s) s=i;
else if(!t) t=i;
else return 0; //发现奇点超过2个,无欧拉路径
}
}
if(s && !t) return 0; //只有1个奇点,无欧拉路径
if(!s) s=1; //没有奇点,有欧拉回路,指定1为起点
dfs(s);
for(k=k-1; k>=0; k--) printf("%d\n", road[k]); //倒序输出答案,也可以不用road数组,使用一个stack
return 0;
}
效率分析:共插入了2E条边,dfs共遍历2E条边。使用multiset可便于快速查找最小值,查找,删除边的复杂度为lgn,故程序时间复杂度为O(Elgn)。