欧拉路径的Fleury算法-C语言实现
我们前面介绍了Hierholzer算法求解有向连通图的欧拉路径(欧拉回路)的算法,这个算法可以简单理解为环环相连,最终求得欧拉路径,整个过程利用深度优先遍历思想,以图中的边为遍历对象,完成遍历后即可求得相应的欧拉路径(欧拉回路),这个算法逻辑清晰,程序运行效率较高。
本文将介绍另外一种欧拉路径的算法,终点介绍无向连通图中的欧拉路径,主要算法为Fleury算法,此算法的核心思想为,如果现有顶点点的度为1,那么和此度相关的唯一路径标记为有效路径,Fleury算法可以立即进行处理;否则就需要利用类似回溯的思想,来评估这些路径是否为bridge. 那么问题就来了,如何定义某顶点到另外一个顶点的路径为bridge呢?
思路很简单,我们先从某顶点(v)出发,评估此顶点直接或间接可达(reachable)的顶点数目,记为count1; 然后我们评估(v,w)是否为桥,这时候,我们在图中先移除此边,如果顶点(v)可达的定点数量count2比count1要小,那么(v,w)就成为一桥边(bridge edge)。无论此边是否为桥边,我们再操作后,都需要把这条边重新加入到图中去。是否不是听起来有点熟悉,这就是回溯的经典思想,后面就这个问题我们在程序中将进一步展开。
一图胜千言,我们借助一张图,来对上面的桥边进行进一步的理解。
读者先暂停几秒,思考一下,如果此图中存在欧拉路径(欧拉回路),起点是哪个? 显而易见,我们应该从C或D出发,才能完成欧拉路径的搜索,起点位置的选择是欧拉路径的性质决定的,只能从奇数点出发(如果是欧拉路径而不存在欧拉回路)。在上图中,如果从C定点出发,我们可以有3个选择,可以选择(C,D)、(C,A)、(C,B),针对这三个选择,我们如何判断哪条边是正确的选择呢(非桥边)。我们利用回溯方法逐一判断知道找到一条非桥边。
如果第一步,我们尝试选择(C,D), 我们从C点出发,在保持(C,D)边的前提下,利用DFS对顶点进行深度优先遍历,得到count 1=4(包括C顶点本身);然后我们尝试从无向图中移除(C,D)边,移除后的D顶点变为孤岛。如果再次从C点开始进行DFS深度优先搜索,我们容易得到count2=3。由于移除(C,D)后,从C点能到达的顶点数量减少,那么我们就认为(C,D)为桥边。
值得注意的是,我们再尝试两次DFS遍历顶点后,需要回复之前移除的边,以便后续能继续访问。恢复边后的图如下:
我们可以尝试评估(C,A)是否为桥边,同理,我们先从C点出发对图上的顶点进行DFS遍历,遍历完成后得到count1=4; 然后,我们尝试去掉(C,A)边,再次进行DFS对顶点的遍历,我们得到count2=4; 由于两次遍历的定点数没有减少,所以我们可以判断(C,A)为非桥边。也就是,我们即使再Fleury算法中真正删除(C,A)后,仍然可以通过其他路径回到C点。
Fleury思想的特点之一,如果每个顶点关联多条路径,在跨出每一步之前,都要确保即使断了这条路,也有可能回到原来的顶点,真正做到即使“过河拆路”,也可能回到原点。
我们回到主题,现在我们解决了桥边的判断问题,如果顶点关联的边多余1条,我们总是从非桥边开始往前遍历。关于桥边的阐述我们可以暂时告一段路,我们接下来就要回到Fleury算法本身。
Fleury算法的核心思想为,找到一条非桥边或独边,以边为遍历对象,逐边进行DFS遍,遍历后,把此边删除(真正删除)。不断利用DFS对边进行遍历,直至遍历结束。
接下来,我们谈一下算法的实现,我们在算法中采用C语言和严蔚敏《数据结构》中的邻接矩阵:
a) 函数的头文件说明(Euler_Path.h):
/**
* @file Euler_Path.h
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-02-11
*
* @copyright Copyright (c) 2023
*
*/
#ifndef EULER_PATH_H
#define EULER_PATH_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "../00_introduction/Status.h"
#include "../01_Matrix_Graph/MatrixGraph.c"
/**
* @brief Find the Euler path(circuit) in the graph
*
* @param G Matrix Graph
* @param path Store the Euler index
* @param degree Degree of each node in the undirected graph
*
* @return Status -return error if there is Eurler path or circuit
*/
Status find_Euler_Path(MGraph *G, int *path,int *degree);
/**
* @brief Count the degree of each node
*
* @param G MGraph G
* @param degree Degree array list
*/
void count_Degree(MGraph G, int *degree);
/**
* @brief Check if the graph contains Euler path or not
* @param G MGraph G
* @param degree Degree array for each node
* @return true if graph contains Euler path
* @return false if graph doesn't contain Euler path
*/
bool is_Euler_Path(MGraph G,int *degree);
/**
* @brief Find the start node in the graph according to the Euler path characteristic
*
* @param G Matrix graph
* @param list Euler_list
* @return int -Node index
*/
int find_Start_Node(MGraph G, int *degree);
/**
* @brief Use depth first search to work out the Euler path/circuit
*
* @param s Start point
* @param list Node property array
* @param path Store the Euler path array
*/
void DFS_Euler(MGraph *G, int s, int *path,int *degree);
/**
* @brief This function returns count of vertices reachable from v.
* It does DFS
*
* @param G Matrix graph
* @param u Start point
* @return int Return the count of vertices reachable from v
*/
int DFS_Count(MGraph G, int u);
/**
* @brief Add the (v,w) edge to Graph
*
* @param G Pointer to Matrix Graph
* @param v Vertex point 1
* @param w Vertex point 2
*/
void Add_Edge(MGraph *G, int v, int w,int *degree);
/**
* @brief Remove the (v,w) edge to Graph
*
* @param G Pointer to Matrix Graph
* @param v Vertex point 1
* @param w Vertex point 2
*/
void Remove_Edge(MGraph *G,int v, int w,int *degree);
/**
* @brief Check if the (v,w) is a valid arc(not a bridge)
*
* @param G Pointer to Matrix graph
* @param v Vertex v
* @param w Vertex w
* @return true If not a bridge, return true
* @return false If a bridge ,return false
*/
bool isValidEdge(MGraph *G,int v,int w,int *degree);
/**
* @brief Show Euler path
*
* @param G Matrix Graph
* @param path Path array
*/
void show_EulerPath(MGraph G, int *path);
/**
* @brief Initialize the degree and path
*
* @param G Matrix Graph
* @param degree Degree of each node in the undirected graph
* @param path Store the path index of Euler path
*/
void Init_Param(MGraph G, int **degree,int **path);
#endif
b) 函数的实现(Euler_Path.c)
/**
* @file Euler_Path.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-02-11
*
* @copyright Copyright (c) 2023
*
*/
#ifndef EULER_PATH_C
#define EULER_PATH_C
#include "Euler_Path.h"
Status find_Euler_Path(MGraph *G, int *path, int *degree)
{
int s;
count_Degree(*G,degree);
if(!is_Euler_Path(*G,degree))
{
return ERROR;
}
s = find_Start_Node(*G,degree);
DFS_Euler(G,s,path,degree);
return OK;
}
void count_Degree(MGraph G, int *degree)
{
int i; //row indicator
int j; //column indicator
for(i=0;i<G.vexnum;i++)
{
for(j=0;j<G.vexnum;j++)
{
if(G.arcs[i][j].adj)
{
degree[i]++;
}
}
}
return;
}
bool is_Euler_Path(MGraph G, int *degree)
{
int i;
int odd_degree;
odd_degree=0;
for(i=0;i<G.vexnum;i++)
{
if(degree[i]%2!=0)
{
odd_degree++;
}
}
return (odd_degree==0) || (odd_degree==2);
}
int find_Start_Node(MGraph G, int *degree)
{
int start;
int i;
start=0;
for(i=0;i<G.vexnum;i++)
{
if (degree[i] % 2 != 0)
{
start=i;
break;
}
}
return start;
}
void DFS_Euler(MGraph *G, int s, int *path, int *degree)
{
int v;
int w;
static int index=0;
v = s;
*(path+index)=v;
index=index+1;
for (w = FirstAdjVex(*G, G->vexs[v]); w >= 0; w = NextAdjVex(*G, G->vexs[v], G->vexs[w]))
{
if (isValidEdge(G, v, w, degree))
{
Remove_Edge(G, v, w, degree);
DFS_Euler(G, w, path, degree);
}
}
}
int DFS_Count(MGraph G, int u)
{
int count;
int v;
int w;
visited[u]=1;
count=1;
v=u;
for (w = FirstAdjVex(G, G.vexs[v]); w >= 0; w = NextAdjVex(G, G.vexs[v], G.vexs[w]))
{
if(!visited[w])
{
count=count+DFS_Count(G,w);
}
}
return count;
}
void Add_Edge(MGraph *G, int v, int w,int *degree)
{
G->arcs[v][w].adj=1;
G->arcs[w][v].adj=1;
degree[v]++;
degree[w]++;
return;
}
void Remove_Edge(MGraph *G, int v, int w,int *degree)
{
G->arcs[v][w].adj=0;
G->arcs[w][v].adj=0;
degree[v]--;
degree[w]--;
return;
}
bool isValidEdge(MGraph *G, int v, int w, int *degree)
{
int count1; //before (v,w) is removed in the graph
int count2; //after (v,w) is removed in the graph
count1=0;
count2=0;
if(degree[v]==1)
{
return true;
}
memset(visited,0,sizeof(int)*MAX_VERTEX_NUM);
count1=DFS_Count(*G,v);
Remove_Edge(G,v,w,degree);
memset(visited, 0, sizeof(int) * MAX_VERTEX_NUM);
count2=DFS_Count(*G,v);
Add_Edge(G,v,w,degree);
return count1<=count2;
}
void show_EulerPath(MGraph G, int *path)
{
int i;
for(i=0;i<G.arcnum+1;i++)
{
printf("-%c-",G.vexs[path[i]]);
}
printf("\n");
}
void Init_Param(MGraph G, int **degree, int **path)
{
*degree=(int *)malloc(sizeof(int)*G.vexnum);
*path = (int *)malloc(sizeof(int) * (G.arcnum+1));
memset(*degree,0,sizeof(int)*G.vexnum);
memset(*path, 0, sizeof(int) * (G.arcnum + 1));
return;
}
#endif
c) 主函数和测试(Euler_Path_main.c)
/**
* @file Euler_Path_main.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-02-12
*
* @copyright Copyright (c) 2023
*
*/
#ifndef EULER_PATH_MAIN_C
#define EULER_PATH_MAIN_C
#include "Euler_Path.c"
int main(void)
{
int *path;
int *degree;
MGraph G;
FILE *fp;
fp=fopen("UDG.txt","r");
CreateGraph(&G,fp);
Init_Param(G,°ree,&path);
find_Euler_Path(&G,path,degree);
show_EulerPath(G,path);
PressEnter;
return EXIT_SUCCESS;
}
#endif
总结,本文的算法略显复杂,但是对DFS的深入理解很有帮助,建议读者能根据Fleury算法原理,尝试自己动手,实现此算法。
以上,
谢谢
参考