C++ 欧拉路相关

I'm Back!请自行模拟州长语音。

五百年啦,我已经快忘了我还有个CSDN帐号了,但是今天,我忙里偷闲诈尸一下,写一篇我最近在学的算法的总结——欧拉路。

定义 Definition

欧拉路分两种,欧拉路以及欧拉回路

欧拉路

欧拉路指的是“在连通图中,通过图中所有边的简单路”,他的基本特征是只有两个或没有奇点。奇点指这个点的入度与出度之和为奇数。

欧拉回路

欧拉回路指“没有奇点,且可以最后回到起点的欧拉路”,特征与欧拉路无异。

思路与实现

对于这种问题,我们一般用深搜DFS来解决,实现起来会比较简单。存图的方法可以酌情选择,对于大多数,我们会选择链式前向星,可是对于断子绝孙无向图,如骑马修栅栏,使用邻接矩阵反而会更加方便,因题而异。

对于普通欧拉路,需要考虑起点和终点。一般的我们称"出度=入度+1"的点为起点,而"入度=出度+1"的点则为终点。

对于欧拉回路,我们不需要考虑起点或终点。涉及到欧拉回路的问题一般是要求字典序最小的,所以只需要找出字典序最小的点作为起点即可。这里可以使用sort排序,待会儿代码中会为大家展示。

另外,只要是无向联通图,就一定存在欧拉回路。

代码

欧拉路的代码比较繁琐,因为需要找起点以及终点。例题:P7771 欧拉路径

#include <bits/stdc++.h>
using namespace std;
vector<int>v[200010];
int n,m,in[200010],out[200010];
int st=1,c[200010];
stack<int>sta;
int a,b;
struct node{
	int v,u;
}e[200010];
bool cmp(node x,node y){
	if(x.u!=y.u){
		return x.u<y.u;
	}
	return x.v<y.v;/*这段CMP代码可以确保字典序最小,如果使用邻接矩阵或者vector的话可以直接写成 
                     这样,不过链式前向星是倒序调用,所以要把x.v<y.v改成大于,这样才对*/
}
void dfs(int p){
	for(int i=c[p];i<v[p].size();i=c[p]){
		int pig=v[p][i];
		if(pig){
			v[p][i]=0;
			c[p]++;
            /*剪枝,当你排除了一些不可用的选项便开始搜下一个,并且这个选项被归回来时,你需要继续            
              搜索别的选项,程序会又一次搜索你曾经搜索过的选项,所以你就需要记录一下你上次搜索到      
              了哪个点,下次直接从这个点搜,可以节省不少时间*/
			dfs(pig);
		}
	}
	sta.push(p);/*使用栈来记录与输出,因为DFS之后存在数组中的答案数列是倒叙,所以可以使用栈先进        
                后出的特点来输出。要是不习惯可以用数组*/
}
int main(){
	cin >> n >> m;
	for(int i=1;i<=m;i++){
		int u,vv;
		cin >> u >> vv;
		e[i].u=u;
		e[i].v=vv;
	}
	sort(e+1,e+1+m,cmp);
//	for(int i=1;i<=m;i++){
//		cout << "Pig" << e[i].u << " " << e[i].v << endl;
//	}
	for(int i=1;i<=m;i++){
		v[e[i].u].push_back(e[i].v);
		out[e[i].u]++;
		in[e[i].v]++;
	}
	for(int i=1;i<=n;i++){
		if(in[i]-out[i]>1||out[i]-in[i]>1){//如果不是欧拉路
			cout << "No";
			return 0;
		}
		if(out[i]==in[i]+1){//找到起点并确定具备起点特征的数的数量
			st=i;
			a++;
		}else if(out[i]==in[i]-1){//确定具备终点特征的数的数量
			b++;
		}
	}
	if(a>1||b>1){//如果有多个起点或终点则不是欧拉路
		cout << "No";
		return 0;
	}
	for(int i=1;i<=n;i++){
		if(out[i]==in[i]+1){
			st=i;
		}
	}
	dfs(st);
	while(!sta.empty()){//如果用数组的话可以另开一个变量ind,来记录数组中有几个数,然后倒叙输出
		cout << sta.top() << " ";
		sta.pop();
	}
	return 0;
}

欧拉回路的代码就会简单一点,具体思路和欧拉路无异。例题:P6066 Watchcow S

#include<bits/stdc++.h>
using namespace std;
int n,m;
struct node{
	int to,next;
}edge[100005];
int tot,ind;
int ans[100005];
int head[100005];
bool vis[100005];
void Add(int v,int u){//链式前向星建边
	tot++;
	edge[tot].to=v;
	edge[tot].next=head[u];
	head[u]=tot;
}
void dfs(int cnt){
	for(int i=head[cnt];i;i=edge[i].next){
		if(!vis[i]){
			vis[i]=1;
			dfs(edge[i].to);
		}
	}
	ans[ind++]=cnt;
}
int main(){
	cin >> n >> m;
	for(int i=0;i<m;i++){
		int u,v;
		cin >> u >> v;
		Add(v,u);
		Add(u,v);//双向建边
	}
	dfs(1);
	for(int i=0;i<ind;i++){
		cout << ans[i] << endl;
	}
	return 0;
}

最后就是所谓的断子绝孙联通图,指的是建边时双向建,但是只要走过这条边,就要双向删除,受用邻接矩阵存图。例题:P1341 无序字母对,先发一个链式前向星,再发一个邻接矩阵,你们可以有一个对比。

链式前向星


//链式前向星
#include<bits/stdc++.h>
using namespace std;
int n,head[100001],du[200];
int a,b,vis[100001],tot,st=200;
stack<int> sta;
struct node{
	int nex,to;
	bool vis=0;
}e[100001];
struct Node{
	int u,v;
}pig[200001];//将数据暂存于一个结构体中,等排完序再存入另一个
void add(int u,int v){
	tot++;
	e[tot].nex=head[u];
	e[tot].to=v;
	head[u]=tot;
}
void dfs(int x){
	for(int i=head[x];i;i=head[x]){
		
		if(!e[i].vis){
			e[i].vis=1;
			for(int j=head[e[i].to];j;j=e[j].nex){
//				cout << "PIG" << endl;
				if(e[j].to==x&&!e[j].vis){//双向删边
//					cout << "Pig" << endl;
					e[j].vis=1;
					break;
				}
			}
//			cout << "Pig" << x << endl;
			head[x]=e[i].nex;
			dfs(e[i].to);
		}else{
			head[x]=e[i].nex;
		}
	}
	sta.push(x);
}
bool cmp(Node x,Node y){
	if(x.u!=y.u){
		return x.u<y.u;
	}
	return x.v>y.v;
}
int main(){
	cin >> n;
	for(int i=1;i<=n;i++){
		string str;
		cin >> str;
		int u=str[0],v=str[1];
		pig[i].u=str[0];
		pig[i].v=str[1];
		pig[n+i].u=str[1];
		pig[n+i].v=str[0];
		du[u]++;
		du[v]++;
		st=min(st,u);
	}
	sort(pig+1,pig+1+2*n,cmp);
//	for(int i=1;i<=n;i++){
//		cout << "Pig" << char(pig[i].u) << " " << char(pig[i].v) << endl;
//	}
	for(int i=1;i<=2*n;i++){
		add(pig[i].u,pig[i].v);
	}
	for(int i=200;i>=0;i--){
		if(du[i]%2==1){
			st=i;
			a++;
		}
	}
	if(a!=2&&a!=0){
		cout << "No Solution";
		return 0;
	}
//	cout << char(st) << "Pig" << endl;
	dfs(st);
	while(!sta.empty()){
		cout << char(sta.top());
		sta.pop();
	}
	return 0;
}

 共85行代码,规模还是挺大的。

//邻接矩阵
#include<bits/stdc++.h>
using namespace std;
int n,du[200];
int a,b,vis[100001],tot,st=200;
stack<int> sta;
int mapp[150][150];
void dfs(int x){
	for(int i=1;i<150;i++){
		if(mapp[x][i]){
			mapp[x][i]=0;
			mapp[i][x]=0;
			dfs(i);
		}
	}
	sta.push(x);
}
int main(){
	cin >> n;
	for(int i=1;i<=n;i++){
		string str;
		cin >> str;
		int u=str[0],v=str[1];
		mapp[u][v]=1;
		mapp[v][u]=1;
		du[u]++;
		du[v]++;
		st=min(st,u);
	}
//	for(int i=1;i<=n;i++){
//		cout << "Pig" << char(pig[i].u) << " " << char(pig[i].v) << endl;
//	}
	for(int i=200;i>=0;i--){
		if(du[i]%2==1){
			st=i;
			a++;
		}
	}
	if(a!=2&&a!=0){
		cout << "No Solution";
		return 0;
	}
//	cout << char(st) << "Pig" << endl;
	dfs(st);
	while(!sta.empty()){
		cout << char(sta.top());
		sta.pop();
	}
	return 0;
}

只有49行代码,可以看出,规模不止缩短了一点,主要就是邻接矩阵的双向删边比链式前向星要方便太多了,所以说遇到这种题就要使用邻接矩阵来做。这种题的特点有:"只能经过一次"、"有多条边"等等,注意辨别,谨防上当受骗。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值