重提七座桥
![](https://i-blog.csdnimg.cn/blog_migrate/a7ce8a548afec3983843a257a4576316.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/d55a0340e61a61d7eb38cb7887a7391b.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/c41c812d4146dc733a67a14e13fe75e1.jpeg)
一笔画定理的严格证明
先定义能一笔画出并回到起点的图为欧拉图,连通就是说任意两个节点之间可以找到一条连接它们的线。这个要求看来很重要,直观方法中与这一点对应的是说原图本身不能是分成多个的。
证明如下:
设 G为一欧拉图,那么G显然是连通的。另一方面,由于G本身为一闭路径,它每经过一个顶点一次,便给这一顶点增加度数2,因而各顶点的度均为该路径经历此顶 点的次数的两倍,从而均为偶数。反之,设G连通,且每个顶点的度均为偶数,欲证G为一欧拉图。为此,对G的边数归纳。当m = 1时,G必定为单结点的环,显然这时G为欧拉图。设边数少于m的连通图,在顶点度均为偶数时必为欧拉图,现考虑有m条边的图G。设想从G的任一点出发,沿 着边构画,使笔不离开图且不在构画过的边上重新构画。由于每个顶点都是偶数度,笔在进入一个结点后总能离开那个结点,除非笔回到了起点。在笔回到起点时, 它构画出一条闭路径,记为H。从图G中删去H的所有边,所得图记为G’,G’未必连通,但其各顶点的度数仍均为偶数.考虑G的各连通分支,由于它们都连 通,顶点度数均为偶数,而边数均小于m,因此据归纳假设,它们都是欧拉图。此外,由于G连通,它们都与H共有一个或若干个公共顶点,因此,它们与H一起构 成一个闭路径。这就是说,G是一个欧拉图。
在 后篇中,我们来设计一个针对玩家的AI,对于玩家来说,如何区分关卡的难度并不是首要的,首要的问题是需要找到一个合理的解,哪怕不是最优的解,也至少是 一个合理的解,这样,用户就不用次次查攻略了,而是他掌握了一个近乎于无敌的攻略——Glow Puzzle的用户版AI。此AI和关卡设计的AI的最大区别在于,该AI假设关卡是合理的,它会返回一条路径,让玩家知道如何快速地通过此关卡。
如图所示,此为该AI的数学模型:
对如上的图,我们将其每个顶点进行标注,然后,读入一个游戏界面的地图(用一个二维整型矩阵来标注),我们的输出可以得到一个可行的解,以便满足玩家的要求。
关于欧拉回路和欧拉路,在Round 17中谈《吴昊教你玩单词接龙游戏》的时候已经备述了,所以,在Round 18中不再赘述,主要说说如何从某个起始点遍历一个一笔画问题,并最终输出一个完整的解(这个解是历经整个顶点的)
这里给出C语言的代码,源码为PASCAL的(这里就是一个翻译的过程,我是人工翻译的,目前还是有PASCAL转C语言的翻译器,只是,貌似还是不太好用,很多地方,包括编码风格啊,缩进啊都不太统一):
两个嵌套的do--while循环略显犀利啊!
Input:
![](https://i-blog.csdnimg.cn/blog_migrate/c7e6ddd8efaa037477b3daf5da7d46ec.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/54fa3fbb58f138a314f29fe22affa7d4.jpeg)
2
3 // 定义游戏界面的规模
4 const int N= 6;
5 // 定义一张选定好的地图
6 int array[N][N]={{ 0, 1, 0, 0, 1, 1},
7 { 1, 0, 1, 1, 0, 1},
8 { 0, 1, 0, 1, 0, 0},
9 { 0, 1, 1, 0, 1, 1},
10 { 1, 0, 0, 1, 0, 1},
11 { 1, 1, 0, 1, 1, 0}};
12
13 int degree[N];
14 int i,j,r,sum,odt,start,now;
15
16 int main()
17 {
18 // 总度数
19 int sum= 0;
20 // 奇点个数
21 int odt= 0;
22 // 默认以第一个结点作为起点
23 int start= 1;
24 for(i= 1;i<=N;i++)
25 {
26 degree[i]= 0;
27 for(j= 1;j<=N;j++)
28 {
29 // 分别相加,统计每个点的度
30 degree[i]=degree[i]+array[i][j];
31 }
32 sum+=degree[i];
33 // 如果该点的度数为奇点的话
34 if(degree[i]% 2== 1)
35 {
36 odt+= 1;
37 // 以奇点作为起始点
38 start=i;
39 }
40 }
41 // 如果奇点大于2的话,就无解了
42 if(odt> 2) printf( " No solution! ");
43 else
44 {
45 // 将当前结点标识为now
46 now=start;
47 printf( " %d ",start);
48 do
49 {
50 int r= 0;
51 /* 找到满足条件的下一个结点
52 下一个结点满足的条件应该是这样的:
53 (1)必须是前一个结点的邻接结点
54 (2)那一点的度数要么大于1,要么等于1就是最简单的总度数为2的情形
55 */
56 do
57 {
58 r=r+ 1;
59 } while(array[now][r]> 0&&((degree[r]> 1)||((degree[r]== 1)&&(sum== 2))));
60 // 将这个邻接矩阵对应的两个元素清0
61 array[now][r]= 0;
62 array[r][now]= 0;
63 // 标记了那两个点之后,将总度数减2
64 sum=sum- 2;
65 // 各点度数减1
66 degree[now]--;
67 degree[r]--;
68 // 定义下一个起点
69 now=r;
70 printf( " -->%d ",r);
71 } while(sum== 0);
72 }
73 return 0;
74 }