大数学家欧拉一生中的大部分时间在俄国和普鲁士度过。1735年,他提出了著名的柯尼斯堡七桥(Seven Bridges of Königsberg)问题:

柯尼斯堡(今俄罗斯加里宁格勒)的市区横跨普雷格尔河两岸,河中心有两个小岛,小岛与河的两岸有七座桥连接。在所有桥都只能走一遍的前提下,如何才能把这个地方所有的桥都走遍?

当时欧拉并没有找到这个问题的解。第二年,他证明了不存在符合条件的走法。在论文中,欧拉将柯尼斯堡的实际情况抽象成了二维空间上点与线的组合,桥可以视为线(边),桥连接的陆地视为点(顶点)——这就是数学中图论思维的起源。

在图论中:

  • 如果在一张图(有向图或无向图)上能够不重复地遍历完所有的边,那么此图就称为 欧拉图 (Eulerian graph)。
  • 能够不重复地遍历完所有的边的路径——即一笔画的“笔画”,称为 欧拉路径 (Eulerian path)。
  • 特别地,如果上述路径的起点与终点相同,则称为 欧拉回路 (Eulerian circuit)。

如下gif所示的图就是欧拉图,存在一个欧拉路径。

  • 必要性
    如果图能够被一笔画成,那么对每个顶点,考虑路径中“进入”它的边数与“离开”它的边数[注意前提是无向图,所以我们不能称其为“入边”和“出边”]。很显然这两个值要么相同(说明该顶点度数为偶),要么相差1(说明该顶点度数为奇)。
    也就是说,如果欧拉路径不是回路,奇度顶点就有2个,即路径的起点和终点;如果是欧拉回路,起点与终点重合,则不存在奇度顶点。必要性得证。

  • 充分性

    • 如果图中没有奇度顶点,那么在G中随机取一个顶点v 0 出发,尝试构造一条回路c 0 。如果c 0 就是原图,则结束;如果不是,那么由于图是连通的,c 0 和图的剩余部分必然存在某公共顶点v 1 ,从v 1 出发重复尝试构造回路,最终可将整张图分割为多个回路。由于两条相连的回路可以视为一条回路,所以该图必存在欧拉回路。
    • 如果图中有2个奇度顶点u和v,那么若是加一条边将u和v连接起来的话,就得到一个没有奇度顶点的连通图,由上文可知该图必存在欧拉回路,去掉这条新加的边,就是一条以u和v为起终点的欧拉路径。充分性得证。

可知,柯尼斯堡七桥问题中的图有4个奇度顶点(1个度数为5,3个度数为3),所以不存在欧拉路径。

有向图的情况

底图连通的有向图G有欧拉路径的充要条件为:G的所有顶点入度和出度都相等;或者只有两个顶点的入度和出度不相等,且其中一个顶点的出度与入度之差为1,另一个顶点的入度与出度之差为1。

显然,可以通过与无向图情况相似的思路来证明,过程略去。

欧拉定理介绍完了,那么如何找到具体的路径呢?

寻找欧拉路径/回路——套圈法

首先,我们当然要判断图的连通性,非连通图是不存在欧拉路径/回路的。判断图的连通性可以通过传统的DFS和BFS方法,也可以通过之前讲过的 并查集 实现,另外还有基于传递闭包的Floyd-Warshall算法(没错就是求最短路的那个),不再赘述。

如果图是连通的,我们再遍历每个顶点的度(有向图就是入度和出度),根据欧拉定理即可轻松地判断图中是否欧拉路径/回路。如果是欧拉路径的话,还能顺便找出路径的起点和终点。

接下来,我们通过俗称“套圈法”的DFS思路来寻找欧拉路径/回路。

参考欧拉定理充分性的证明过程,欧拉图可以分割为多个相交的回路,所以我们可以从起点开始,通过DFS逐渐扩展路径,并标记边已经被遍历过(根据定义,已经被标记了的边之后就不会再走),直到形成一个回路。然后回溯到上一个有边没被遍历到的顶点——就是上文说的“c 0 和图的剩余部分必然存在某公共顶点v 1 ”,以它为起点重复同样的操作,直到所有回路都被找出来。在回溯阶段所记录的路径就是所求的欧拉路径/回路。

听起来似乎有些混乱,来看一道例题吧。

例题——POJ 2337 «Catenyms»

传送门见 这里

题目大意:给定N个单词,要求把这些单词不重复地排成一个序列,使每个单词的首字母与前一个单词的末尾字母相同(e.g. aloha.aloha.arachnid.dog.gopher.rat.tiger ),以点号分隔输出。如果存在不止一个解,输出字典序最小的那个序列。如果没有解,输出三个星号。

我们可以将每个单词的两端视为顶点,单词本身视为有向边,就能构造出有向图。先判断连通性,再判断是否存在欧拉路径/回路(同时找出起点),最后用套圈法找出具体的路径——由于DFS回溯得到的路径是倒序的,所以把它们放在栈中记录比较方便。

本题需要特别注意的点在于输出字典序最小的那个序列,因此先要将所有单词按字典序排序。另外,如果存在的是欧拉回路,那么得选择字典序最小的单词作为起点。

今天时间不太够了,AC代码之后再补上,看官可参考其他大佬的代码,或移步Discuss区。

The End

寻找欧拉路径/回路也可以使用 Fleury算法 ,但是它要额外检测图中的桥边,相比DFS而言不太容易操作。看官可以参考图论或离散数学教材获取更多细节。

民那晚安。

柯尼斯堡七桥问题大数学家欧拉一生中的大部分时间在俄国和普鲁士度过。1735年,他提出了著名的柯尼斯堡七桥(Seven Bridges of Königsberg)问题:柯尼斯堡(今俄罗斯加里宁格勒)的市区横跨普雷格尔河两岸,河中心有两个小岛,小岛与河的两岸有七座桥连接。在所有桥都只能走一遍的前提下,如何才能把这个地方所有的桥都走遍?当时欧拉并没有找到这个问题的解。第二年,他证明了不存在符... 哥尼斯堡是位于普累格河上的一座城市,它包含两个岛屿及连接它们的七座桥,如下图所示。 可否走过这样的七座桥,而且每桥只走过一次?瑞士数学家 欧拉 (Leonhard Euler,1707—1783)最终解决了这个 问题 ,并由此创立了拓扑学。 这个 问题 如今可以描述为判断 欧拉回路 是否存在的 问题 欧拉回路 是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条 回路 。现给定一个无向图,问是否存在 欧拉回路 ? 输入格式: 输入第一行给出两个正整数,分别是节点数N (1≤N≤100 我们先定义寄点是跟这个点相连的边数目有奇数个的点。对于能够 一笔画 的图,我们有一下两个定理。 定理1:如果一张图存在 一笔画 且不会回到起点,这个图有且只有2个寄点。(前提是图是连通的)。我们称这样的 路径 欧拉 路。。。所以我们也可以知道这玩意是谁恁出来的了。。。。。。。 定理2:如果一个张图存在 一笔画 且最后回到起点,这个图没... 对给定的一个无向图,判断能否 一笔画 出。若能,输出 一笔画 的先后顺序,否则输出“No Solution!” 所谓 一笔画 出,即每条边仅走一次,每个顶点可以多次经过。 输出字典序最小的 一笔画 顺序。 第1行:1个整数n,表示图的顶点数(n 接下来n行,每行n个数,表示图的邻接矩阵 第1行: 一笔画 的先后顺序, 第一行只有一个正整数N(N<=10)表示测试数据的组数。 每组测试数据的第一行有两个正整数P,Q(P<=1000,Q<=2000),分别表示这个画中有多少个顶点和多少条连线。(点的编号从1到P) 随后的Q行,每行有两个正整数A,B(0<A,B<P),表示编号为A和B的两点之间有连线。 如果存在符合条件的连线,则输出"Yes", 如果不存在符合条件的连线,输出"No"。 对给定的一个无向图,判断能否 一笔画 出。若能,输出 一笔画 的先后顺序,否则输出“No Solution!” 所谓 一笔画 出,即每条边仅走一次,每个顶点可以多次经过。 输出字典序最小的 一笔画 顺序。 第1行:1个整数n,表示图的顶点数(n 接下来n行,每行n个数,表示图的邻接矩阵 第1行: 一笔画 的先后顺序,每个顶点之间用一个空格分开
1. 图论 起源 众所周知, 图论 起源 于一个非常经典的 问题 —— 柯尼斯堡 (Konigsberg) 七桥 问题 。1738年,瑞典数学家 欧拉 (Leornhard Euler)解决了 柯尼斯堡 七桥 问题 。由此 图论 诞生, 欧拉 也成为 图论 的创始人。 欧拉 问题 的实质归于" 一笔画 " 问题 ,即判断一个图是否能够遍历完所有的边 (Edge) 而没有重复,而 柯尼斯堡 七桥 问题 则是 一笔画 问题 的一个具体情境。 欧拉 证明这个 问题 不成立。 满足 一笔画 的图满足两个条件: 图必须是一个完整图 有零个或二个奇数点 2. 图和节点 图是一组节点和连接这些节
var y = e.changedTouches[0].y; this.data.ctx.beginPath(); this.data.ctx.moveTo(this.data.lastX, this.data.lastY); this.data.ctx.lineTo(x, y); this.data.ctx.stroke(); this.setData({ lastX: x, lastY: y, onTouchEnd: function (e) { this.setData({ isDrawing: false, onTouchCancel: function (e) { this.setData({ isDrawing: false, onClear: function () { this.data.ctx.clearRect(0, 0, 1000, 1000); this.data.ctx.draw(); 3. 在 wxml 文件中添加事件监听: <canvas canvas-id="myCanvas" style="width: 100%; height: 100%;" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" bindtouchcancel="onTouchCancel" ></canvas> <button bindtap="onClear">清除</button> 4. 在 css 文件中设置画布样式: canvas { background-color: #fff; border: 1px solid #999; touch-action: none; 通过以上代码,就可以实现一个简单的 一笔画 玩法的微信小程序。需要注意的是,这只是一个最基本的实现,还可以添加更多的功能和交互效果来丰富游戏体验。