csp 201512-4 送货(hierholzer算法的递归和堆栈实现)

原题【传送门】
该题为求解欧拉路的问题。

一、背景知识

欧拉路:给定无孤立节点的图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分)
第二个参考的代码真的厉害,可惜有些没看懂。
大家都看到这里了,点个赞吧,嘿嘿~

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值