- 上一篇博文中,我们提到欧拉路径有Hierholzer 和 Fleury 算法,Hierholzer相比而言逻辑清晰,而且比较有效。本文终点介绍Hierholzer的基本算法。Hierholzer算法利用深度优先搜索(DFS),最后完成所有的边的遍历。在图论中,常规的DFS算法的操作对象为顶点,这就导致可能已经完成所有顶点的遍历,但是有些边还没有遍历(比如backedge 或 forward edge类型)。我们以示意为例进行说明,
图中包含12条有向边,注意②->②成为自环有向边(不一定严谨),从顶点①出发,经过顶点③,再经过顶点⑤,而后经过顶点⑥,再遍历顶点②以及顶点④,最后回到顶点⑥,这时候显示全部遍历完成,无需再遍历任何顶点。
实际情况是,5条白色的边仍然未遍历。如果采用上述方式,那么就无法完成欧拉路径(回路)的遍历过程。 - 如何解决上述问题
要解决上述问题,我们就需要利对边进行标记,以边为对象进行深度优先搜索遍历,这样就可以确保所有的边都可以被遍历到。对于上述顶点,我们可以建立以下属性来跟踪每个顶点对应的边的访问情况,对于每个顶点我们定义in为入度数,定义out为出度数,同时我们跟踪邻接表的访问的顶点指针,用edge_ptr表示。 edge_ptr将被初始化为所有顶点的firstarc(可能为NULL),在每次访问后,向后移动,记录下一个边的指针(edge_ptr->nextarc). 我们可以通过判断edge_ptr是否为空,来确认DFS的访问是否需要结束。
/-------------------------严蔚敏《数据结构》邻接表定义--------------------------/
typedef struct ArcNode
{
int adjvex; //Vertex location this arc points to 本弧所指向的顶点
struct ArcNode *nextarc; //Pointer pointing to next Arc 指向下一条弧指针
InfoType *info; //Related information pointer about THIS Arc,该弧相关指针
}ArcNode;
typedef struct VNode
{
VertexType data; //Vertex informaiton
ArcNode *firstarc; // First arc incident to this Arc
}VNode,AdjList[MAX_VERTEX_NUM];
/-------------------------严蔚敏《数据结构》邻接表定义------------------------/
typedef struct Euler_Node
{
int in;
int out;
ArcNode *edge_ptr;
}Euler_Node, Euler_List[MAX_VERTEX_NUM];
- 利用Hierholzer算法进行DFS递归之前,还有几个问题需要亟待解决,首先需要判定有向图是否包含欧拉路径或欧拉回路,这就需要计算每个顶点的出度和入度大小,然后根据定义进行判断。计算入度、出度和此顶点边的指针,下面的初始化函数将完成此任务。
//@param list数组定义在结构体当中
void Init_list(ALGraph G, Euler_List list)
{
int i;
int v;
int w;
ArcNode *p;
v=0;
//初始化list数组
for(i=0;i<G.vexnum;i++)
{
list[i].edge_ptr=G.vertices[i].firstarc;
list[i].in=0;
list[i].out=0;
}
for(v=0;v<G.vexnum;v++)
{
for(p=G.vertices[v].firstarc;p;p=p->nextarc)
{
w=p->adjvex;
list[v].out++;
list[w].in++;
}
}
}
利用各个顶点的出度和入度的相对大小比较,那么就能相对容易判断一个有向图(本文中的研究对象)是否含有欧拉路径(欧拉回路),根据欧拉路径或回路的定义,我们给出下列函数完成欧拉路径或欧拉回路的计算。
bool is_Euler_Path(ALGraph G, Euler_List list)
{
int start_node;
int end_node;
int i;
start_node=end_node=0;
for(i=0;i<G.vexnum;i++)
{
//check whether Euler path is valid nor not
//First check the diffrence between indegree and outdegree
if((list[i].out-list[i].in>1) || (list[i].in-list[i].out>1))
{
return false;
}
else if (list[i].out - list[i].in==1)
{
start_node++;
}
else if (list[i].in - list[i].out == 1)
{
end_node++;
}
}
return ((start_node==1 && end_node==1) || (start_node==0 && end_node==0));
}
当上述问题解决之后,我们还需要确认欧拉路径的起始点(欧拉回路可以选择任一点),在欧拉路径中,如果起始点选择不正确,那么就会导致无法完成正确的遍历。寻找欧拉路径(欧拉回路)起始点的函数描述如下。
int find_Start_Node(ALGraph G, Euler_List list)
{
int start=0;
int i;
for(i=0;i<G.vexnum;i++)
{
if((list[i].out-list[i].in==1) && list[i].out>0)
{
start = i;
break;
}
}
return start;
}
- 上述问题解决之后,我们就可以利用DFS函数,并辅助线性表,对有向图进行欧拉路径或欧拉回路的搜索。 这是典型的DFS搜索,只是对象为边而不是顶点。
void DFS_Euler(ALGraph G, int s, Euler_List list, LinkList_E *path)
{
Link_E link;
ArcNode *p;
int w;
while(list[s].edge_ptr)
{
p=list[s].edge_ptr;
w=p->adjvex;
list[s].edge_ptr=p->nextarc;
list[s].out--;
DFS_Euler(G,w,list,path);
}
MakeNode_E(&link,s); //参见严蔚敏线性表
InsFirst_E(path,path->head,link);//参考严蔚敏先线性表,头部插入节点
}
- 把上述所有的函数组织起来,实现Hierholzer算法的最后目的。
Status find_Euler_Path(ALGraph G, Euler_List list, LinkList_E *path)
{
int s;
// initialize the list[i].in, list[i].out and list[i].edge_ptr
Init_list(G,list);
InitList_E(path);
if(!is_Euler_Path(G,list))
{
return ERROR;
}
s=find_Start_Node(G,list);
DFS_Euler(G,s,list,path);
return OK;
}
6.总结
我们可以形象的把Hierholzer算法理解为,不同的欧拉路径(欧拉回路)之间互相联结的过程。整个过程为大环套小环,最终环环紧相联(one splice)的过程。
以上,
谢谢
参考文献/资料:
- 《数据结构》严蔚敏
- Video, Finding Eulerian Paths and Circuits by William Fiset