吴昊品游戏核心算法 Round 18 —— 吴昊教你玩Glow Puzzle(后篇)
重提七座桥
一笔画定理的严格证明
先定义能一笔画出并回到起点的图为欧拉图,连通就是说任意两个节点之间可以找到一条连接它们的线。这个要求看来很重要,直观方法中与这一点对应的是说原图本身不能是分成多个的。
证明如下:
设 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:
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 }