吴昊品游戏核心算法 Round 18 —— 吴昊教你玩Glow Puzzle(后篇)

  

   哎呀,糟糕了呀!我忘记说了,其实最高级别不是AWESOME,而是EXCELLENT,这应该是根据时间的快慢以及是否起始点和终止点为同一个点来共同判断的吧!

  重提七座桥

 

 

  

  柯尼斯堡七桥问题图论中的著名问题。这个问题是基于一个现实生活中的事例:当时东普鲁士柯尼斯堡(今日俄罗斯加里宁格勒)市区跨普列戈利亚河两岸,河中心有两个小岛。小岛与河的两岸有七条桥连接。在所有桥都只能走一遍的前提下,如何才能把这个地方所有的桥都走遍?
  

 

  

  莱昂哈德·欧拉1735年圆满地解决了这一问题,并在第二年发表在论文《柯尼斯堡的七桥》中,证明符合条件的走法并不存在,也顺带提出和解决了一笔画问题[1]。这篇论文在圣彼得堡科 学院发表,成为图论史上第一篇重要文献。欧拉把实际的抽象问题简化为平面上的点与线组合,每一座桥视为一条线,桥所连接的地区视为点。这样若从某点出发后 最后再回到这点,则这一点的线数必须是偶数,这样的点称为偶顶点。相对的,连有奇数条线的点称为奇顶点。欧拉论述了,由于柯尼斯堡七桥问题中存在4个奇顶 点,它无法实现符合题意的遍历。
 

 

  

欧拉把问题的实质归于一笔画问题,即判断一个图是否能够遍历完 所有的边而没有重复,而柯尼斯堡七桥问题则是一笔画问题的一个具体情境。欧拉最后给出任意一种河──桥图能否全部走一次的判定法则,从而解决了“一笔画问 题”。对于一个给定的连通图,如果存在两个以上(不包括两个)奇顶点,那么满足要求的路线便不存在了,且有n个奇顶点的图至少需要n/2笔画出。如果只有 两个奇顶点,则可从其中任何一地出发完成一笔画。若所有点均为偶顶点,则从任何一点出发,所求的路线都能实现,他还说明了怎样快速找到所要求的路线。[1]

  一笔画定理的严格证明

  先定义能一笔画出并回到起点的图为欧拉图,连通就是说任意两个节点之间可以找到一条连接它们的线。这个要求看来很重要,直观方法中与这一点对应的是说原图本身不能是分成多个的。

  证明如下:

  设 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

  在 后篇中,我们来设计一个针对玩家的AI,对于玩家来说,如何区分关卡的难度并不是首要的,首要的问题是需要找到一个合理的解,哪怕不是最优的解,也至少是 一个合理的解,这样,用户就不用次次查攻略了,而是他掌握了一个近乎于无敌的攻略——Glow Puzzle的用户版AI。此AI和关卡设计的AI的最大区别在于,该AI假设关卡是合理的,它会返回一条路径,让玩家知道如何快速地通过此关卡。

  如图所示,此为该AI的数学模型:

 

  

  对如上的图,我们将其每个顶点进行标注,然后,读入一个游戏界面的地图(用一个二维整型矩阵来标注),我们的输出可以得到一个可行的解,以便满足玩家的要求。

  关于欧拉回路和欧拉路,在Round 17中谈《吴昊教你玩单词接龙游戏》的时候已经备述了,所以,在Round 18中不再赘述,主要说说如何从某个起始点遍历一个一笔画问题,并最终输出一个完整的解(这个解是历经整个顶点的)

  这里给出C语言的代码,源码为PASCAL的(这里就是一个翻译的过程,我是人工翻译的,目前还是有PASCAL转C语言的翻译器,只是,貌似还是不太好用,很多地方,包括编码风格啊,缩进啊都不太统一):

  两个嵌套的do--while循环略显犀利啊!

  Input:

 

 

  

  Output:
 

 

 

 1  #include<stdio.h>
 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  }

 

 


 


 

posted on 2013-04-24 11:02  吴昊系列  阅读(964)  评论(0编辑  收藏  举报

导航