欧拉路径

“一笔画”问题,对于有向图和无向图

基本概念

对于连通图:

  • 欧拉路径:不重复地遍历完所有边
  • 欧拉回路:经过欧拉路径回到起点 ,欧拉回路也是欧拉路径
  • 半欧拉图:图中存在欧拉路径
  • 欧拉图:图中存在欧拉回路,欧拉图也是半欧拉图

非连通图一定没有欧拉路径

判断

先判断是否是连通图

对于非起点和非终点,入这个点的边数需要等于出这个点的边数,才能保证每条边只走一次

无向图

  • 存在欧拉路径的充要条件:图中度为奇数的点为0个(欧拉回路)或2个(起点和终点不同)
  • 存在欧拉回路的充要条件:图中度为奇数的点为0个

有向图

  • 存在欧拉路径的充要条件:图中所有点的出度等于入度(欧拉回路)或起点的出度等于入度加一,终点的入度等于出度加一,非起点和非终点的入度等于出度
  • 存在欧拉回路的充要条件:图中所有点的入度等于出度

Hierholzer算法

首先找到一个子回路,并逐步将其他回路合并到该子回路中,最终形成完整的欧拉回路

有向图/无向图的欧拉路径/欧拉回路可以拆成从起点到终点的直线,欧拉路径不唯一

先找到一条不完整的子回路,确定终点,再排除这个点,回溯,找另一个回路,确定终点的上一个点,直到所有子回路找完

  1. 选择一个合理的点作为起点,开始dfs,对于欧拉图,找任一点都可以作为起点,对于非欧拉图的半欧拉图,找度为奇数的点(无向图)或出度等于入度加一的点(有向图)作为起点
  2. 遍历邻边,经过的边都标记,标记过的边不能访问
  3. 若当前遍历的节点没有可走的边,将此节点入栈
  4. 遍历完成后将栈边输出边出栈,得到欧拉路径

Pasted image 20260330093454

如图,每个回路中省略了其它节点

  1. 先找到回路1,节点1入栈,回溯到节点3
  2. 找到回路2,节点3入栈,回溯到节点4
  3. 找到回路3,节点4入栈,依次回溯到节点6,7,5,2,1,并入栈
  4. 最终确定1-->3-->4-->6-->7-->5-->2-->1的逆欧拉路径,逆序输出得到欧拉路径

实现

struct edge{
	int u;
	int next;
	int uid;    //边的编号
	bool visited;
}edges[2*MaxE];
vector<int> ans1;    //用点表示路径(逆序)
vector<int> ans2;    //用边表示路径(逆序)

void dfs(int x){
	int u;
	for(int i=head[x];i!=-1;i=edges[i].next){
		if(edges[i].visited)cintinue;
		//若i是偶数,i^1=i+1,若i是奇数,i^1=i-1,根据存边的顺序,边和反向边是一奇一偶的关系,标记边需要将该边的反向边一起标记
		edges[i].visited=edges[i^1].visited=true;
		dfs(edges[i].u);
		ans2.push_back(edges[i].uid);    //由于dfs传参只传点,所以只能在下层dfs结束后收录边
	}
	ans1.push_back(x);
}

对于一个有x条边的点,这个点会被访问x/2次(一次进入一次出去),所以对于有e条边的图,整个图会被遍历e/2次,每访问一个节点,需要遍历所有邻边,所以总的时间复杂度为O(E*(E+V))

优化

每访问过一条边,直接将这条边删掉,就不用每个点都遍历所有邻边

struct edge{
	int u;
	int next;
	int uid;    //边的编号
	bool visited;
}edges[2*MaxE];
vector<int> ans1;    //用点表示路径(逆序)
vector<int> ans2;    //用边表示路径(逆序)

void dfs(int x){
	int u;
	for(int i=head[x];i!=-1;i=head[x]){
		if(edges[i].visited){    //第二次访问同一条边时执行删除
			head[x]=edges[i].next;    //头删
			continue;
		}
		edges[i].visited=edges[i^1].visited=true;
		//head[x]=edges[i].next    若在第一次访问后就删边,删不掉反向边,若是有向图可以在访问后直接删
		dfs(edges[i].u);
		ans2.push_back(edges[i].uid);
	}
	ans1.push_back(x);
}

时间复杂度O(E+V)

求字典序最小的欧拉路径

由于dfs是先遍历到的点后入栈,所以先遍历到的点在欧拉路径中靠前,所以需要尽可能先遍历编号小的点,需要对每个点的出度排个序(小到大)

posted @ 2026-04-18 16:04  mofei1116  阅读(55)  评论(0)    收藏  举报