C++ 图论算法之欧拉路径、欧拉回路算法(一笔画完)

这篇具有很好参考价值的文章主要介绍了C++ 图论算法之欧拉路径、欧拉回路算法(一笔画完)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

公众号:编程驿站

1. 欧拉图

本文从哥尼斯堡七桥的故事说起。

哥尼斯堡城有一条横贯全市的普雷格尔河,河中的两个岛与两岸用七座桥连结起来。当时那里的居民热衷于一个话题:怎样不重复地走遍七桥,最后回到出发点。这也是经典的一笔画完问题。

1736年瑞士数学家欧拉(Euler)发表了论文《哥尼斯堡七桥问题》。论文中使用图论理论解决哥尼斯堡七桥问题,欧拉图由此而来。论文中欧拉证明了如下定理:一个非空连通图当且仅当每个顶点的度数都是偶数时才会是欧拉图。

欧拉图的几个概念:

  • 欧拉回路:指在图(无向图或有向图)中,经过图中所有边且只经过边一次所形成的回路,称为欧拉回路。具有欧拉回路的图称为欧拉图。如下图结构为欧拉图,从1号节点出发,经过所有边后可以重回到1号节点。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

  • 欧拉路径:指通过图中每条边且仅通过一次形成的路径(没有环)。具有欧拉路径但不具有欧拉回路的图称为半欧拉图。如下图,从6号节点出发,可以经过每一条边后到达2号节点,存在欧拉路径,只能说是半欧拉图。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

欧拉图的性质:

  • 欧拉图中所有顶点的度数都是偶数。也就是说,图中存在欧拉回路的充要条件是图中每个结点都是偶节点(连接该节点的边的数量为偶数)。

因为欧拉回路定义只能经过每条边一次,所以,对于每一个节点,至少需要有 2n(n=0,1……) 条边连接该节点。

论证:当 n = 0时,图结构中只含有一个节点v,边数为0,图论中认为自己和自己是能构建成回路的。所以当n=0时,图是欧拉图。

n>=1时,如果从一个节点出发,经过一个路径后,能够重新回来。相当于一个人要和其他人围成一个圈,每个人必须伸出两只手,否则是不可能形成圈的。故每个节点都连接有2n(n = 0,1,2,...n)条边。

  • 欧拉路径中奇节点(连接该节点的边的数量为奇数)的个数02。若奇节点的个数为0,则图中存在欧拉回路,欧拉回路也是欧拉路径的一种。把欧拉回路变成欧拉路径,只需要抽取出环中的一条边。因为欧拉环的充要条件是节点度数有偶数,抽取出一条边后,会让原来连接边两端的节点的度数分别减少一,出现两个奇节点。

    除此之外,你不能再抽取出任何一条边,否则得不到欧拉路径。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

  • 若图是欧拉图,则它为若干个环的并集,且每条边被包含在奇数个环内。如下图,整个图是由5个环组成,且每一条边都是包含在奇数个环内。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

欧拉图的判定法:

无向图是欧拉图当且仅当:非零度顶点是连通的;顶点的度数都是偶数。

无向图是半欧拉图当且仅当:非零度顶点是连通的;恰有 2 个奇度顶点。

有向图是欧拉图当且仅当:非零度顶点是强连通的;每个顶点的入度和出度相等。

有向图是半欧拉图当且仅当:非零度顶点是弱连通的;至多一个顶点的出度与入度之差为 1;至多一个顶点的入度与出度之差为 1;其他顶点的入度和出度相等。

2. 欧拉图判定算法

2.1 Fleury(弗罗莱) 算法

Fleury算法用来判断图是否是欧拉通路或欧拉回路的算法。

使用如下的欧拉图,了解Fleury算法的主要步骤。

Tips: 根据欧拉图的判断法,下图中每一个节点都是偶节点,满足无向图是欧拉图的前提条件。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

  • 选节点1为起点,并将该起点加入路径中。Fleury算法选择栈存储欧拉路径。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

  • 从起点开始,一路DFS试着走出一条通路。方法是找与此节点相邻的节点。

    如果只有一个节点,则将这个点直接加入路径中。

    如果有多个相邻节点,则选择其中一条边,把相邻节点加入路径后,且删除这一条边。

    如果没有邻接节点,则从路径中弹出。

    节点5和节点2都与1相邻,可以选择向5方向,也可以选择2方向。这里选择2方向,把节点2放入路径,然后置1-2这条边为删除状态。如此这般,一路经过3、4、5节点后回到1号节点。下图中标记为红色的边表示已经访问或被删除。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

  • 重新回到节点1,此时不再存在与节点1邻接的节点,从路径中弹也,依次可弹出5、4、3。直到碰到2号节点。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

  • 因为存在与2号节点邻接的节点,再次以2号节点为始点,使用DFS开路。一路上遇到6、7,且再次回到2号节点。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

  • 2号节点不存在与之邻接的节点,出栈。同理,7、6依次出栈。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

小结:

当有与当前节点邻接的节点时,一路DFS,直到没有邻接的尽头。些时,一轮DFS算法结束,从路径中依次弹出没有邻接节点的节点,直到遇到还有邻接节点的节点,新一轮的DFS重新开始。直到所有节点邻接的边全部访问完毕。

编码实现:

#include <iostream>
#include <math.h>
#include <algorithm>
#include <cstring>
#include <stack>
#define INF 100000
using namespace std;
int graph[100][100];
int n,m;
stack<int> sta;
void read() {
	for(int i = 0; i < m; i++) {
		int f,t;
		cin >> f >> t;
		graph[f][t] = 1;
		graph[t][f] = 1;
	}
}
void dfs(int u) {
	sta.push(u);
	for(int i = 1; i <= n; i++) {
		if(graph[i][u] > 0) {
			//标记为删除
			graph[u][i] = 0;
			graph[i][u] = 0;
			dfs(i);
			//仅朝一条边方向 DFS,方便形成回路 
			break;
		}
	}
}
void fleury(int x) {
	int  isEdge;
	sta.push(x);
	while(!sta.empty()) {
		isEdge = 0;
		int t = sta.top();
		sta.pop();
		//检查是否有边
		for(int i = 1; i <= n; i++) {
			if(graph[t][i] > 0) {
				isEdge = 1;
				break;
			}
		}
		if(isEdge == 0) {
			//没有邻接边,输出
			cout << t << " ";
		} else {
			//有邻接边,一路DFS狂奔
			dfs(t);
		}
	}
}
int main() {
	cin >> n >> m;
	memset(graph,0,sizeof(graph));
	read();
	int num = 0;
	int start = 1;
	for(int i = 1; i <= n; i++) {
		int deg = 0;
		for(int j = 1; j <= n; j++)
			deg += graph[i][j];
		if(deg % 2 == 1) {
			//奇节点的数量
			start = i;
			num++;
		}
	}
	if(num == 0 || num == 2)
		fleury(start);
	else
		cout << "不存在欧拉路径" << endl;
	return 0;
}
//测试用例
7 8
1 2
1 5
2 3
2 6
2 7    
3 4
4 5
6 7    

测试结果:

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

2.2 Hierholzer 算法

也称逐步插入回路法。由数学家卡尔·希尔霍尔策给出,基于贪心思想。Hierholzer 的基本思路。先找到一个子回路,以此子回路为基础,逐步将其它回路以插入的方式合并到该子回路中,最终形成完整的欧拉回路。继续使用上图做演示。

  • 寻找子回路:如下从节点1开始,沿着边遍历图,一边遍历一边删除经过的边。如果遇到一个所有边都被删除的节点,那么该节点必然是 1(回到初始点)。将该回路上的节点和边添加到结果序列中。这个过程和Fleury算法没有太多区别。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

  • 回溯时检查刚添加到结果序列中的节点,看是否还有与节点相连且未遍历的边。可发现节点 2 有未遍历的边,则从 2 出发开始遍历,找到一个包含 2 的新回路,将结果序列中的一个 2 用这个新回路替换,此时结果序列仍然是一个回路。这是和Fleury算法最大区别。

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

  • 重复直到所有边都被遍历。

编码实现

#include<iostream>
#include<string.h>
#include<vector>

const int maxn = 10005;
const int maxm = 1000005;//edge
using namespace std;
int n,m;
struct Edge {
	int to, nxt;
	bool vis=0;
};
Edge edge[maxm];
//如果没有以 i 为起点的有向边则 head[i] 的值为 0
int head[maxm];
//边的个数
int cnt;
//存储找到的回路
vector<Edge> ans;
//起始点
int sn;

void init() {
	for(int i=1; i<=n; i++) {
		head[i]=0;
		cnt=0;
	}
}

/*
*添加边
*/
void addEdge(int from, int to) {
	edge[cnt].to = to;
	edge[cnt].nxt = head[from];
	head[from] = cnt++;
}
void read() {
	int f,t;
	for(int i=1; i<=m; i++) {
		cin>>f>>t;
		addEdge(f,t);
		addEdge(t,f);
	}
}
void hierholzer(int sn) {
	for (int i = head[sn]; i != 0; i = edge[i].nxt) {
		// 遍历过
		if (edge[i].vis) continue;
		// 删除
		edge[i].vis = edge[i ^ 1].vis = true;
		// 继续
		hierholzer(edge[i].to);
		// 回溯时加入结果序列后,循环会继续查找是否有邻接边
		ans.push_back(edge[i]);
        
	}
}
void show() {
	for(int i=0; i<ans.size(); i++) {
		cout<<ans[i].to<<"\t";
	}
	cout<<sn<<"\t";
}

int main() {
	cin>>n>>m;
	sn=1;
	init();
	read();
	hierholzer(sn);
	show();
	return 0;
}

测试结果:

欧拉路径问题,c++,图论,算法,欧拉,欧拉回路

3. 总结

HierholzerFleury算法的基本思路差不多,在DFS时找环。Fleury使用分段策略,找到一条环后,以环中某一个还存在邻接边的节点重新开始使用DFS找环,直到找到所有环。Hierholzer算法很有技巧性,在回溯时检查节点是否还有邻接边,有则重新DFS直到完毕。文章来源地址https://www.toymoban.com/news/detail-854515.html

到了这里,关于C++ 图论算法之欧拉路径、欧拉回路算法(一笔画完)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 图论(欧拉路径)

    理论: 所有边都经过一次,若欧拉路径,起点终点相同,欧拉回路 有向图欧拉路径:恰好一个out=in+1,一个in=out+1,其余in=out 有向图欧拉回路:所有in=out 无向图欧拉路径:两个点度数奇,其余偶 无向图欧拉回路:全偶 基础练习 P7771 【模板】欧拉路径 P2731 [USACO3.3] 骑马修栅栏

    2024年02月05日
    浏览(34)
  • 图论10-哈密尔顿回路和哈密尔顿路径+状态压缩+记忆化搜索

    求解哈密尔顿回路 如何求解一个图是否存在哈密尔顿回路呢? 一个最直观的想法就是暴力求解。暴力求解的思路也很简单:我们遍历图的每一个顶点 v,然后从顶点 v 出发,看是否能够找到一条哈密尔顿回路。 暴力求解的代价同求解全排列问题是等价的,其时间复杂度为

    2024年02月04日
    浏览(37)
  • 【图论C++】Floyd算法(多源最短路径长 及 完整路径)

    UpData Log👆 2023.9.29 更新进行中 Statement0🥇 一起进步 Statement1💯 有些描述可能不够标准,但能达其意 常见的有: DJ算法 、 Floyd算法 、 A*算法 、 Bellman-Ford 算法 、 SPFA算法 其中 A*算法 是 DJ算法 的plus版, SPFA算法 是 Bellman-Ford 算法 的plus版 算法名称 DJ算法 Floyd算法 SPFA算法

    2024年02月19日
    浏览(43)
  • C++算法 —— 动态规划(2)路径问题

    每一种算法都最好看完第一篇再去找要看的博客,因为这样会帮你梳理好思路,看接下来的博客也就更轻松了。当然,我也会尽量在写每一篇时都可以让不懂这个算法的人也能边看边理解。 动规的思路有五个步骤,且最好画图来理解细节,不要怕麻烦。当你开始画图,仔细阅

    2024年02月06日
    浏览(50)
  • 【图论】关键路径求法c++

    代码结构如下图: 其中topologicalSort(float**, int, int*, bool*, int, int)用来递归求解拓扑排序,topologicalSort(float**, int*, int, int, int)传参图的邻接矩阵mat与结点个数n,与一个引用变量数组topo,返回一个布尔值表示该图是否存在拓扑排序,同时将引用变量数组topo赋值为该图的拓扑序列

    2024年02月03日
    浏览(42)
  • 图论中回路与圈的概念区分

    第一种定义方法 迹 是边不重复的通路,但是顶点可以重复。 回路 是首尾顶点相同的迹。 路 是顶点不重复的迹,即边和顶点都不重复的通路,但是首尾顶点可以相同。 圈 是首尾顶点相同的路。 第二种定义方法 回路:起点终点相同 简单通路:起点到终点所经过的 边不同 

    2024年02月04日
    浏览(44)
  • 数据结构第13周 :( 迪杰斯特拉最短路径 + 弗洛伊德求最短路径 + 欧拉回路 + Invitation Cards)

    【问题描述】 在带权有向图G中,给定一个源点v,求从v到G中的其余各顶点的最短路径问题,叫做单源点的最短路径问题。 在常用的单源点最短路径算法中,迪杰斯特拉算法是最为常用的一种,是一种按照路径长度递增的次序产生最短路径的算法。 在本题中,读入一个有向图

    2024年02月13日
    浏览(40)
  • 408【数据结构】图、生成树、图的出度和入度、路径and路径长度和回路、简单路径和简单回路概念整理 和 错题整理

            图由顶点集V和边集E组成,记为G=(V,E),使用 V(G) 表示 所有顶点的集合(不能为空) ;使用 E(G) 表示 各个顶点之间的关系(可以为空) 。若用V={v1,v2,v3,....,vn}来表示图,则使用 |V|表示图中顶点的个数, 使用E={(vi,vj)|vi∈V,vj∈V},用 |E| 表示图中 边的条

    2024年02月03日
    浏览(44)
  • 离散数学 --- 图论基础 --- 图的同构,通路与回路,可达性与最短通路

    同一个图(这里的图是抽象的数学定义)可以有不同的图形表示方法 1.重数:两点之间的平行边的个数   1.得到 n! 的过程,一个图中的一个结点在另一个图中对应的结点有n种可能(黄框中定义的图来讨论),这个对应好后下一个结点有 n - 1 种可能,再下一个有n-2种,直到最

    2024年01月25日
    浏览(43)
  • 【C++算法竞赛 · 图论】图论基础

    前言 图论基础 图的相关概念 图的定义 图的分类 按数量分类: 按边的类型分类: 边权 简单图 度 路径 连通 无向图 有向图 图的存储 方法概述 代码 复杂度 图论(Graph theory) ,是 OI 中的一样很大的一个模块,围绕它有很多高难度的算法以及高级的概念。 这篇文章将介绍关

    2024年04月12日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包