原题【传送门】
该题为求解欧拉路的问题。
一、背景知识
欧拉路:给定无孤立节点的图G,若存在一条路,经过图中每一边一次且仅一次,则该路称为欧拉路。
欧拉回路:若存在一条回路,经过图中的每一边一次且仅一次,则该回路称为欧拉回路。
欧拉图:具有欧拉回路的图称为欧拉图。
判定定理:无向图G具有一条欧拉路,当且仅当G是连通的,且有0个或2个奇数度结点。
--------以上来自左孝凌的《离散数学》
二、题目分析
给定一个图,存在欧拉路,则依次输出其经过结点;如果存在多条,则输出字典序最小的;如果不存在,则输出-1;
由于欧拉回路是一种特殊的欧拉路,所以判定定理仍然有效。题目中没有特殊要求是回路,则都按照欧拉路处理即可。
由判定定理,需要先判断:是连通图且只有0或2个奇数度结点。判断连通图可用并查集实现。
接着采用Hierholzer 算法求出欧拉路。
三、Hierholzer 算法
递归思路:
1.判断奇点数。奇点数若为0则任意指定起点,奇点数若为2则指定起点为奇点。
2.开始递归函数Hierholzer(x):
循环寻找与x相连的边(x,u):
删除(x,u)
删除(u,x)
Hierholzer(u);
将x插入答案队列之中
3.倒序输出答案队列。
堆栈思路:
1.判断奇点数。奇点数若为0则任意指定起点,奇点数若为2则指定起点为奇点,将起点入栈。
2. 若栈不为空:
取栈顶元素 x(区别弹栈)
x 连接的边数为0:
将该元素加入答案队列
弹栈
x 连接的边数不为0:
寻找与其相连的边 (x,u)
删除(x,u)
删除(u,x)
u入栈
3.倒序输出答案队列。
四、代码
#include<stdio.h>
#include<vector>
#include<set>
using namespace std;
int n,m;
const int maxn=10001;
set<int> edges[maxn];//set自然成升序,每次取出第一个即可。
vector<int> ans;
int father[maxn];
int find_father(int s){//寻找祖宗
int a=s;
while(father[a]!=a){//找到祖宗为:a
a=father[a];
}
while(s!=a){//回溯,减小高度进而减小时间复杂度
int tmp=s;
s=father[s];
father[tmp]=a;
}
return a;
}
bool test(){//检测是否存在欧拉路
int fnum=0;
for(int i=1;i<=n;i++){//数连通子图的个数
if(father[i]==i) fnum++;
}
if(fnum>1) return 0; //非联通图
int num=0;
for(int i=1;i<=n;i++){//奇数节点个数
if(edges[i].size() %2==1){ //更好的写法:if(edges[i].size()&1){...}
num++;
}
}
if(num==2) {//如果是2个奇数节点,检测1是否为奇数度。
if(edges[1].size()%2==1) return 1; //或者:return edges[1].size()%2
else return 0;
}
else if(num==0) return 1;
else return 0;
}
void dfs(int s){//递归写法,只有80分,爆栈
while(!edges[s].empty()){
int v=*edges[s].begin() ;
edges[s].erase(v);
edges[v].erase(s);
dfs(v);
}
ans.push_back(s);
return;
}
void dfs1(int s){//栈写法,满分
path.push_back(s);
while(!path.empty() ){
int cur=path.back() ;
if(edges[cur].empty()){
ans.push_back(cur);
path.pop_back() ;
}
else{
int v=*edges[cur].begin() ;
edges[cur].erase(v);
edges[v].erase(cur);
path.push_back(v);
}
}
}
int main(){
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<=n;i++){
father[i]=i;
}
for(int i=0;i<m;i++){
scanf("%d%d",&u,&v);
int uf=find_father(u);
int vf=find_father(v);
if(uf!=vf) father[vf]=uf;//新读入的边将两个连通子图连通
edges[u].insert(v);
edges[v].insert(u);
}
if(!test()){
printf("-1");
return 0;
}
dfs1(1);
for(int i=ans.size() -1 ;i>=0;i--){//倒序输出
printf("%d ",ans[i]);
}
return 0;
}
五、参考与感想
欧拉回路求解算法
CCF CSP 竞赛试题——送货(201512-4)(真的100分)
第二个参考的代码真的厉害,可惜有些没看懂。
大家都看到这里了,点个赞吧,嘿嘿~