图论起源与欧拉回路
- 图论起源
- 一笔画问题通解(回溯)
- 欧拉回路
- 欧拉回路的存在性
- Fleury寻找欧拉回路(贪心)
- Hierholzer寻找欧拉回路(模拟)
图论是生活中的一个抽象的概念或者说是工具,围绕多对多的连接关系,计算机科学家们设计了很多算法,而后把很多实际问题抽象出来,用图论的算法解决多对多的信息或者数学问题。
关于图的算法有很多,但最重要的是图的遍历算法,也就是如何从一个点出发,通过连接的线访问图的各个点。
图要是学好了,您可以跨学科研究:社会网络、数据分析、离散数学、网络爬虫、道路规划、机器人导航、系统动力学…而系统动力学是真的好玩。
图,这种结构起源于大数学家欧拉,我今天要拿一些专业名称来唬人,请不要揭穿我。
比如说,奇点
、偶点
、起点
、终点
、顶点
、度数
。
“请大家翻到 95
页,思考 3m
… (其实读一下题就好) ”,小学数学老师如是说。
这是小学数学书上的一道题,好像是二年级… (比划过,有些印象)
这道题是图论的起源,由大数学家 莱昂哈德·欧拉 采用一笔画的解法证明在当前给定的条件下,不能走遍哥尼斯堡七桥。
我们来走一遍,试试。
起点:陆地A的店主桥
,不重复走过的桥并且全部走过,终点:内福夫岛
。
[陆地A的店主桥]
出发,经过[店主桥]
, 抵达[内福夫岛]
;(但没有走过所有桥,继续)[内福夫岛]
出发,经过[铁匠桥]
,抵达[陆地A]
;[陆地A]
出发,经过[木桥]
,抵达[陆地C]
;[陆地C]
出发,经过[密桥]
,抵达[内福夫岛]
;[内福夫岛]
出发,经过[绿桥]
,抵达[陆地D]
;[陆地D]
出发,经过[吉布莱茨桥]
,抵达[内福夫岛]
;漏了[高桥]
!!!
生活在那里的人们,晚上散步也会去试试说不定就全部走了一遍,可惜就是没走出来。
后来,欧拉再次把上面的图简化为几何图形!!
连接方式不变,简化后的连接方式,就是【图】(Graph)。
A、B、C、D
不在是陆地了,是叫【顶点】,而 7
座桥现在叫【边】。
解决这个问题其实非常简单,对于这个几何图形,我们只需要考虑入口和出口。
您看,顶点A
是不是有3
条线连在A
的圈圈上,所以,顶点A
有3
个出入口,度数为 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 条边,边走边减
的方法也叫【一笔画】。
回溯算法:
#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 条边,边走边减
欧拉回路想让每条边都走一遍,回到原点,那每个点都必须有进有出。
那每个点的相连边数(度数),必须是偶数。
如果图存在欧拉回路,每个点的度数是偶数。
那如果一个图,它的每个点的度数是偶数,图一定存在欧拉回路吗?
如果这个图是联通的,而且是无向图,就成立 — 其余的就不成立。
如果我们要判断一个图是否存在欧拉回路,就可以采用这个简明的证明:
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算法步骤:
输入:
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
复杂度:
Fleury
的时间复杂度还可以优化为 Θ ( e ∗ l o g e 3 ) Theta(e*loge^{3}) Θ(e∗loge3),具体的优化方法比较复杂,需要查找相应的论文。
思路:从一个点出发,随意走。
其实就是把上面的数学证明 【一个无向联通图,它的每个点的度数是偶数,图一定存在欧拉回路】的过程,给模拟下来了。
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
复杂度:
这个算法会删除边,可以用深拷贝优化,保护原始数据不受影响。
本文发布于:2024-02-01 17:14:17,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170677891138206.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |