Euler Graph - 欧拉图 详解

1.哥尼斯堡七桥问题

哥尼斯堡七桥问题:
18世纪中叶在欧洲普鲁士的哥尼斯堡城内有一条贯穿全市的和河中有两个小岛,现在四块陆地有七座桥连接,引入问题,如何规划线路才能保证我们可以走过所有的边但是却保证不会重复呢

这就是著名的哥尼斯堡七桥问题,现在通过图论证明哥尼斯堡问题是不存在解的,第一个发表论文证明了这个事实的人就是欧拉,该论文也是目前为止发现的最早的关于图论的论文,当然,这种问题也就被命名为欧拉图的问题,现在我们即将从图论的角度来仔细的阐释一下欧拉图的相关定理以及证明,最后附上相应的求解模板代码

2.欧拉图

首先我们需要先来了解一些有关欧拉图的性质和定义:

1.定义:

1.我们的定义的数据对象的范围是有向图和无向图
2.图的通路:
图中的顶点与边的交替序列我们称之为是图的通路
3.欧拉通路:
我们从有向图或者无向图中的任意一点出发,将所有的边遍历且仅遍历一次的通路序列我们称之为是欧拉通路
4.欧拉回路:
如果我们的欧拉通路的起点和终点是一样的我们称之为欧拉回路
5.欧拉图:
具有欧拉回路的图称之为欧拉图,规定平凡图(只有一个顶点的空图)属于欧拉图
6.半欧拉图:
具有欧拉通路的图我们称之为是半欧拉图

2.定理:

1.无向图G是欧拉图的充分必要条件是G 是连通图并且没有奇数度顶点
证明:
平凡图显然成立
必要性:图G是欧拉图,证明G中没有奇数度节点
G是欧拉图——G中存在欧拉回路——欧拉回路中每个点每出现在欧拉回路的序列中就必定会获得两个度——所以欧拉序列中的所有的点必然都是偶数度的节点——不存在奇数度的节点
充分性:G中没有奇数度的节点,证明G是欧拉图——数学归纳法
假设边数是m
1.m=1,没有奇数度节点——该边只能是一个环——G是欧拉图
2.假设m<=k成立,现在证明m=k+1成立
G连通且没有奇数度顶点——G中必然存在圈——删去圈上的所有的边——假设获得了s个连通分量,每个连通分量有最多k条边,并且都是偶数度的节点(圈上的边每条给顶点贡献两个度)——根据上述的归纳假设每个连通分量都是一个欧拉图——我们现在将圈复原——必然存在一条欧拉回路连接了所有的节点并回到原点(这条回路的主路就是刚才删去的圈,每次进入连通分量的时候,遍历连通分量的欧拉回路在出来继续走圈)

2.无向图G是半欧拉图的充分必要条件是G是连通的并且恰好有两个奇数度顶点
必要性:图G是半欧拉图,证明G中恰好有两个奇数度的顶点
G是半欧拉图,存在一条欧拉通路——通路上非端点节点必然是偶数度的——通路上的两个端点,必然都是奇数度
充分性:图G是连通的并且恰好有两个奇数度的顶点,证明G是半欧拉图
假设现在我们向G中添加一条连接两个奇数度顶点的边,那么必然满足欧拉图的性质——存在欧拉回路——那么我们将欧拉回路中的那条新加的边删去就会的到一条端点是两个奇数度顶点的欧拉通路
定理2拓展,如果我们想要一笔画完一个半欧拉图我们,假设半欧拉图中有2*k个边,我们最少需要k笔才可以画完这个半欧拉图,原因就是每一次遍历走完两个奇数度顶点之间的欧拉通路,知道所有的奇数度顶点都走完位置图就遍历完了

3.有向图G是欧拉图的充分必要条件是图是强连通,并且每个定点的入度等于出度
4.有向图G是半欧拉图的充分必要条件是图单向连通并且恰好有两个奇数度顶点,这两个奇数度顶点一个的出度比入度大1,一个刚好反过来,其余的顶点的出度等于入度

5.G是非平凡的欧拉图当且仅当G是连通的并且是若干个边不重合的圈的并

3.求解欧拉回路:

现在我们已经了解了什么是欧拉回路了,我们现在开始着手来书写找寻欧拉回路的算法

1.逐步插入回路法
根据我们上面的定理1的充分性的证明,我们其实已经得到了一种求解欧拉回路的算法,那就是我们找到了一个圈,我们从圈开始,每次找到一个连通分量就进入走完连通分量的回路,知道我们的主路的圈全部走完,那么我们的走过的序列就是一个欧拉回路
现在这个算法的的描述我们已经很清楚了,但是实现呢?
这是一个很麻烦的问题,机缘巧合之下,我读了一些来自POJ2230的源代码,本题是有向图的欧拉回路的模板题,从这道题,我是真的明白了求解欧拉回路的算法——逐步插入回路法
本算法的精髓在于DFS深度优先搜索
因为遍历了G所有的边和顶点,时间复杂度是O(e+v)
先附上伪代码:
DFS_Graph(root):
    for (root,j) in Graph:
        if visit[(root,j)]==0:
            visit[(root,j)]=1
            DFS_Graph(j)
    add root in answer
短短几行代码,我们就交代了逐步插入回路法的精髓,但是想要真正理解这段代码是非常的不容易的
首先,我们需要注意,这段代码对于有向图和无向图都是适用的
下面我们来模拟一下这个深搜的过程: CSDN没有办法很好的上传图片,我在这里简单的模拟一下
本图是一个有向图

1——2
|    |
|    |
3——
|
|
4
如上所示,1,2,3,4之间有连边,现在开始模拟
我们从1开始深搜(随机的,别的情况也会出现,这里模拟最容易看清本质的搜索过程)
1——>2——>3——>1——>3——>2——>1
这时候我们会发现回到原点了,但是显然我们并没并且1走到了死胡同里,算法除了问题吗?
并不是,我们接着看
DFS搜索到1,这时候并没有出边开始执行add操作,我们将1节点加入最后的欧拉回路序列
函数递归回溯,2依然没有多余的的出边add 2
函数递归回溯,3这时有多余的出边指向4,继续递归至顶点4
1——>2——>3——>1——>3——>4——>3
又回到了顶点3这时候,顶点3也没有多余的出边,add 3                      目前的序列有:1,2,3
函数回溯至顶点4,顶点4没有多余的出边,add 4
1——>2——>3——>1——> 3
函数回溯至顶点3,3没有多余的出边,add 3
1——>2——>3——>1 add1
1——>2——>3 add 3
1——>2 add 2
1 add 1
最终结果1 2 3 4 3 1 3 2 1

通过上面的模拟我们已经可以发现了这个逐步插入回路法的DFS的实现的精髓了
实际上我们第一次搜索会起点的时候,就是找到了一个圈,然后我们函数回溯找每个节点是否还有多余的出边,有多余的出边就从这个入口节点我们就继续递归下去(进入一个连通分量),直到返回到入口节点,直到入口节点没有多余的出边的时候我们这时开始回溯一次回溯连通分量,将连通分量加入结果序列直到将整个圈上的所有的顶点都回溯完之后,返回最开始的起点,欧拉回路查找完毕

当然,大家会说这是有向图的算法啊,无向图其实也是一样的思路和想法
先附上有向图的代码:
POJ 2230
/*
Problem: 2230		User: lantianheyeqi
Memory: 3776K		Time: 860MS
Language: C++		Result: Accepted
*/
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cstdlib"
#define N 100005

using namespace std;
//实现的数据结构是用链式前向星模拟邻接表
int u[N];
int v[N];
int next[N];
int first[N];
int n,m;
bool book[N];

void dfs(int root)
{
	int k=first[root];
	while(k!=-1)
	{
		if(book[k]!=1)
		{
			book[k]=1;
			dfs(v[k]);
		}
		k=next[k];
	}
	printf("%d\n",root);
}

int main()
{
	memset(book,0,sizeof(book));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) scanf("%d%d",&u[i],&v[i]);
	memset(first,-1,sizeof(first));
	for(int i=1;i<=2*m;i++)
	{
		next[i]=first[u[i]];
		first[u[i]]=i;
	}
	dfs(1);
	return 0;
}

4.Fleury算法

Fleury(弗洛莱)算法
所谓的更为简单的求解欧拉回路的算法
首先我们需要了解一下边割集合的概念:
我们定义在图G中,如果我们删去边集E得到GN,导致图的连通分量的个数p(GN)>p(G),并且G删去E的任何一个子集都不会导致p(G)增加,那么我们定义E为G的一个边割集,如果边割集中只有一个元素,我们成该边为

Fleury算法:
Euler Graph - 欧拉图 详解_第1张图片
证明不会,但是我需要讲解几点算法的实现的步骤
1.首先我们想要快速的判断一个边是不是桥,这里我们需要引入Tarjan算法,时间复杂度是O(E+V)
2.之后我们遍历完整个图找到一条欧拉回路,算法的时间复杂度是O(E)
3.整体的时间复杂度大致是O(E+V)

5.遗留问题

1.Tarjan算法
2.为什么都说Fleury比DFS的效率高
3.求解半欧拉图(欧拉通路)的算法
4.无向图的补充

你可能感兴趣的:(算法精讲,算法杂论,图,ACMer's,collection,算法与数据结构,POJ,专项题解)