4. 图论起源与欧拉回路

阅读: 评论:0

4. 图论起源与欧拉回路

4. 图论起源与欧拉回路

图论起源与欧拉回路

    • 图论起源
    • 一笔画问题通解(回溯)
    • 欧拉回路
    • 欧拉回路的存在性
    • Fleury寻找欧拉回路(贪心)
    • Hierholzer寻找欧拉回路(模拟)

 


图论起源

图论是生活中的一个抽象的概念或者说是工具,围绕多对多的连接关系,计算机科学家们设计了很多算法,而后把很多实际问题抽象出来,用图论的算法解决多对多的信息或者数学问题。

关于图的算法有很多,但最重要的是图的遍历算法,也就是如何从一个点出发,通过连接的线访问图的各个点。

图要是学好了,您可以跨学科研究:社会网络、数据分析、离散数学、网络爬虫、道路规划、机器人导航、系统动力学…而系统动力学是真的好玩。
 


图,这种结构起源于大数学家欧拉,我今天要拿一些专业名称来唬人,请不要揭穿我。

比如说,奇点偶点起点终点顶点度数

“请大家翻到 95 页,思考 3m… (其实读一下题就好) ”,小学数学老师如是说。

这是小学数学书上的一道题,好像是二年级… (比划过,有些印象)

这道题是图论的起源,由大数学家 莱昂哈德·欧拉 采用一笔画的解法证明在当前给定的条件下,不能走遍哥尼斯堡七桥。

我们来走一遍,试试。


起点:陆地A的店主桥,不重复走过的桥并且全部走过,终点:内福夫岛

  • [陆地A的店主桥]出发,经过[店主桥], 抵达[内福夫岛];(但没有走过所有桥,继续)
  • [内福夫岛]出发,经过[铁匠桥],抵达[陆地A];
  • [陆地A]出发,经过[木桥],抵达[陆地C];
  • [陆地C]出发,经过[密桥],抵达[内福夫岛];
  • [内福夫岛]出发,经过[绿桥],抵达[陆地D];
  • [陆地D]出发,经过[吉布莱茨桥],抵达[内福夫岛];

漏了[高桥]!!!

生活在那里的人们,晚上散步也会去试试说不定就全部走了一遍,可惜就是没走出来。

后来,欧拉再次把上面的图简化为几何图形!!


连接方式不变,简化后的连接方式,就是【图】(Graph)。

A、B、C、D 不在是陆地了,是叫【顶点】,而 7 座桥现在叫【边】。

解决这个问题其实非常简单,对于这个几何图形,我们只需要考虑入口和出口。

您看,顶点A是不是有3条线连在A的圈圈上,所以,顶点A3个出入口,度数为 3

由此类推,其他顶点:

  • B:5
  • C:3
  • D:3

度数严谨的定义:顶点所关联的边数。

因为每个顶点都关联多条边(多个度数),但每通过顶点一次,这个顶点就减去 2 个度数。

如果想一次性走成,必须满足所有顶点都是偶数或者只有俩个奇点。

我们顺着图走,边走边减。

每经过一个顶点,就划去 2 条边,边走边减。 因为一次减2条边,每个顶点的奇偶性都不会变。

[陆地A]出发,A的度数减1只有出口 -> 经过[店主桥]到达[陆地B]B的度数减2因为经过入口和出口;

其余步骤同理。验证结果如下,

  • 起点和终点相同,则 A、B、C、D 顶点度数都要为偶数
  • 起点和终点不同,则 A、B、C、D 起点、终点度数为奇数,其余顶点度数要为偶数

而我们不需要回到顶点,所以是第2种情况,即起点和终点不同。

起点和终点不同,走遍七桥并不重复,结果是所有顶点度数都为0

如果还存在大于0的顶点,那就还有没走过的边。

那倒推过来,起点、终点顶点度数为奇数其余顶点度数要为偶数,才能全部走完。

  • A:3
  • B:5
  • C:3
  • D:3

对比一下,发现原图的七桥并不匹配,所以不可能在不走重复桥的前提下,走遍7桥。

 


一笔画问题通解(回溯)

这种每经过一个顶点,就划去 2 条边,边走边减 的方法也叫【一笔画】。

  • 先计算出图的度数、每个顶点的度数;
  • 模拟走的过程,每经过一个顶点,就划去 2 条边,边走边减;
  • 当 走过顶点数 等同 总顶点数 时,结束;
  • 如果没有路了,但还有顶点没走,要退回继续尝试。

回溯算法:

#include <stdio.h>
const int Q = 4;/* 第一步,建模 [ 邻接矩阵 ]  */
int graph[Q][Q]= {0,1,1,0,1,0,1,1,1,1,0,1,0,1,1,0,
}; 										// 顶点C在七桥中是只能走一边,所以C点要全部赋值为0即无解情况/* 第二步,统计每个顶点度数和奇点个数 */
int a[Q];		                        // 记录Q个顶点的度数
int total;								// 顶点数总计
int edge;								// 记录走过的顶点int draw(int v) {int k = 0;if( total == edge ) return 1;  // 递归结束条件:走过顶点数 等同 总顶点数for(int i = 0; i < Q; i++) {          // 1 - Q-1if( graph[v][i] == 1 ) {k = 1;graph[v][i] = 0; graph[i][v] = 0;edge += 2;if( draw(i) ) {                   // 如果递归能走下去printf(" -> %c", i+65);         // 输出顶点return 1;} else {                          // 否则,悔一步要复原graph[v][i] = 1;graph[i][v] = 1;edge -= 2;k = 0;}}}if(k == 0) return 0;
}int main(int argc, char *argv[]) {int v = 1; 	// 若没有奇点则从顶点1开始int k = 0;	// 奇点个数总计for( int i = 0; i < Q; i ++ ) {for( int j = 0; j < Q; j ++)if( graph[i][j] == 1 )   a[i]++;     // 统计每个顶点的度total += a[i];   					   if( a[i]%2 == 1 ) {                    // 判断当前顶点度数是否为奇点k ++;v = i;}}if( k > 2 )            printf("No solution, k > 2n");    // 奇点大于2,无解情况else{draw(v);                           // 从俩个奇点任意一点出发printf(" -> %c ",v+65);}return 0;
}

回溯法是一种【选优搜索法】,按照选优条件深度优先搜索,以达到目标。当搜索到某一步时,发现原先选择并不是最优或达不到目标,就【退回】一步重新选择,走不通就退回再走

回溯复杂度:

  • 时间复杂度: Θ ( 2 n ) Theta(2^n) Θ(2n)
  • 空间复杂度: Θ ( n ) Theta(n) Θ(n)
     

欧拉回路

欧拉回路就是欧拉证明【哥尼斯堡七桥】问题无解后而得名的,欧拉回路即从一个点出发,沿着边行走,经过每个边恰好一次,最后再回到出发点。

欧拉回路,其实就是对【哥尼斯堡七桥】问题的描述。

目前求解欧拉回路,一共有三种算法:

  • 回溯: Θ ( 2 n ) Theta(2^{n}) Θ(2n)
  • Fleury: Θ ( e 2 ) Theta(e^{2}) Θ(e2)
  • Hierholzer: Θ ( v + e ) Theta(v+e) Θ(v+e)

回溯法,我们已经实现了,就是上面的那个。

解决欧拉路径问题:

  • 先判断回路是否是欧拉回路
  • 再用最优的算法寻找欧拉回路

 


欧拉回路的存在性

欧拉回路即从一个点出发,沿着边行走,经过每个边恰好一次,最后再回到出发点。

欧拉证明欧拉回路的方法很清晰,主要看度数。

每经过一个顶点,就划去 2 条边,边走边减

欧拉回路想让每条边都走一遍,回到原点,那每个点都必须有进有出。

那每个点的相连边数(度数),必须是偶数。

如果图存在欧拉回路,每个点的度数是偶数。

那如果一个图,它的每个点的度数是偶数,图一定存在欧拉回路吗?

如果这个图是联通的,而且是无向图,就成立 — 其余的就不成立。


如果我们要判断一个图是否存在欧拉回路,就可以采用这个简明的证明:

  • 判断图是否是联通的, D F S 、 B F S DFS、BFS DFS、BFS 都可以
  • 如果联通分量 > 1 > 1 >1,条件就不成立,不存在欧拉回路
  • 遍历图的所有顶点,如果某个点是奇数,也不存在欧拉回路
bool has_euler_loop( adjMatrix * G ){if( DFS( G ) > 1 )   // DFS 返回 联通分量的个数return false;for (int i=0; i<G->n; i++ )if( degree(G->n) % 2 == 1 )    // degree 返回 顶点的度数return false;return true;
}

欧拉路径的存在条件:

  • 无向图:仅有两个点度数为奇数,且是连通图(用并查集判断);
  • 有向图:有两个点可以入度出度不相等(差不大于一),即起点终点;起点入度小于出度,终点入度大于出度,且是连通图(用并查集判断)。

欧拉回路的存在条件:

  • 无向图:所有点度数都为偶数,且是连通图(用并查集判断);
  • 有向图:所有点的出度入度都相等;从任意一点都可实现,且是连通图(用并查集判断)。

 


Fleury寻找欧拉回路(贪心)

思路:从某个点开始,遍历边,但有一个挑选条件,要先选【多边】的点走,不走单边的顶点。

贪心就体现在,要先选【多边】的顶点走,因为对比回溯,贪心就在于看出来走【多边】比走单边要好得多,避免做无用功。

如果一个顶点的邻边只有一条,也被称为【桥】(走这条路会把图分成俩部分)。

因为就像现实生活里的桥一样,如果拆掉河水上的桥,那把这张陆地(图)分割为俩个部分,不联通所以走不通。

Fleury算法步骤:

  • 对当前点的邻边,判断一下是否是【桥】
  • 如果存在多边,就不走【桥】,选一条多边的走就可
  • 否则,只能走【桥】
  • 其余的,和回溯法相同

输入:

5 6
1 2
1 3
2 3
3 4
3 5
4 5

输出:

1 3 5 4 3 2 1

完整代码:

#include <iostream>
using namespace std;#define M 202
typedef long long ll;
struct stack {int top, node[M];
}s;int e[M][M],n;
void dfs(int x) {int de[+&#p]=x;for(i=0;i<n;i++) {if(e[i][x]>0) {e[i][x]=e[x][i]=0;  //删除这条边dfs(i);break;}}
}
void fleury(int x) {int i,p=0; s.p]=x;p>=0) {flag=0;for(i=0; i<n; i++) {if(p]][i]>0) {flag=1;break;}}if(!flag) printf("%d ",s.p--]+1);else p--]);}puts("");
}int main( )
{int i,j,u,v,m,degree,num=0,start=0;scanf("%d %d",&n ,&m);memset(e, 0, sizeof(e));for(i=0;i<m;i++) {scanf("%d %d",&u, &v);e[u-1][v-1] = e[v-1][u-1] = 1;}for(i=0; i<n; i++) {degree = 0;for(j=0;j<n;j++)degree+=e[i][j];if(degree & 1) {start=i;num++;}}if(num==0||num==2) fleury(start);else printf("No Euler pathn");return 0;
}

Fleury复杂度:

  • 时间复杂度: Θ ( e 2 ) Theta(e^{2}) Θ(e2)
  • 空间复杂度: Θ ( n ) Theta(n) Θ(n)

Fleury 的时间复杂度还可以优化为 Θ ( e ∗ l o g e 3 ) Theta(e*loge^{3}) Θ(e∗loge3),具体的优化方法比较复杂,需要查找相应的论文。

 


Hierholzer寻找欧拉回路(模拟)

思路:从一个点出发,随意走。

其实就是把上面的数学证明 【一个无向联通图,它的每个点的度数是偶数,图一定存在欧拉回路】的过程,给模拟下来了。

  • 选择任一顶点为起点,遍历所有相邻边;
  • 深度搜索,访问相邻顶点。将经过的边都删除;
  • 如果当前顶点没有相邻边,则将顶点入栈;
  • 栈中的顶点倒序输出,就是从起点出发的欧拉回路。
void remove_edge( adjMatrix *nG, int v, int w )
{
if( nG->kind == DN || nG->kind == UDN )  // 无向图nG->matrix[v][w] = nG->matrix[w][v] = 0;elsenG->matrix[v][w] = 0;
}void __hierholzer(adjMatrix *nG, std::stack<int>&s, int v){      for(int i=0; i<nG->n; i++)if(nG->matrix[v][i]){remove_edge( nG, v, i );__hierholzer(nG, s, i);// 不用恢复边!相当于删除边}s.push(v);         // 出栈时记录
}// 深拷贝:保护数据
void hierholzer( adjMatrix *G ){deep_copy(G, nG);std::stack<int> s;__hierholzer( nG, s, 0 );while(!s.empty()){printf("%d ",s.top());s.pop();}printf("n");
}

输入:

5 6
1 2
1 3
2 3
3 4
3 5
4 5

输出:

1 2 3 4 5 3 1

Hierholzer复杂度:

  • 时间复杂度: Θ ( v + e ) Theta(v+e) Θ(v+e)
  • 空间复杂度: Θ ( n ) Theta(n) Θ(n)

这个算法会删除边,可以用深拷贝优化,保护原始数据不受影响。
 


本文发布于:2024-02-01 17:14:17,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170677891138206.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:回路   起源   欧拉   图论
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23