最近想做个拼图游戏,一是作为一个小项目练手,二来学学github使用,三来学习一下TDD。关于内容的顺序,第一篇作为项目分析,接下来几篇记录开发过程,并写下总结反思。
本篇内容介绍:
1. 游戏规则说明
2. 拼图的可还原性分析
3. 逆序数
一、游戏规则
首先,这里的拼图游戏是滑块拼图,类似于华容道,游戏者通过移动拼图块将拼图还原为初始形状。关于拼图,常见的有3x3,4x4,多的以至于有16x16不等。一般块数越多拼图越复杂。
这里对游戏规则进行较严格的定义:
1. 游戏初始化时,将图形分为nxn个正方形块,随机摆放在原图形位置。一般以原图形右下角的一块为空白块,已进行移动。这里初始化时,也令新图形的右下角为空。
2. 游戏中,拼图的移动只能向空白块移动。从移动方向来说,有两种情况,分别是横移与纵移。
3. 游戏的胜利条件是将打乱的图形恢复原位置。
二、拼图的可还原性
以下论述为了简便,以数字代表拼图的原始顺序。下图为3x3拼图的正确结果:
1 | 2 | 3 |
---|---|---|
4 | 5 | 6 |
7 | 8 |
1.不可能还原的拼图情况
从上图可以看到,虽然看上去只要交换14和15便能胜利,但实际上并不能做到,因此是一种不可能还原的拼图。事实上,若拼图是随机打乱的,理论上只有1/2的可能性能被还原。以3x3拼图为例,可以通过移动归结为以下两类:
A类:可还原拼图
1 | 2 | 3 |
---|---|---|
4 | 5 | 6 |
7 | 8 |
B类:不可还原拼图
1 | 2 | 3 |
---|---|---|
4 | 5 | 6 |
8 | 7 |
从数学上可以证明,A类与B类是不等价的,即不能通过有限次移动将A类变成B类。下面将进行证明。
首先介绍逆序数。
2.逆序数
2.1定义
在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。逆序数为偶数的排列称为偶排列;逆序数为奇数的排列称为奇排列。如2431中,21,43,41,31是逆序,逆序数是4,为偶排列。
2.2计算方法
从网上可以搜到三种常见解法,分别是直接枚举,归并排序和树状数组。这里不再详述。
3.证明:A类拼图与B类拼图不等价
在上面,我们已经介绍了逆序数的定义以及奇排列与偶排列的划分。接下来我们可以看到,A类拼图与B类拼图的本质区别在于其排列的奇偶性不同。
将拼图中的数字以行优先顺序展开,可以得到如下序列:
类别 | 序列 | 奇偶性 |
---|---|---|
A类 | 12345678 | 偶排列 |
B类 | 12345687 | 奇排列 |
可以看到,可以还原的一类拼图,其展开后的序列为偶排列,而不可还原的一类拼图,其展开后的序列为奇排列。
3.1引理:拼图的移动不改变序列的奇偶性
从规则定义中,我们可以看到,拼图块的移动分为行移动和列移动,以下分别证明。
行移动:
1 | 2 | 3 |
---|---|---|
4 | 5 | 6 |
7 | 8 |
以上图为例,由于移动是向空白块移动,所以行移动并不会改变空白块两边的数字的相对位置,如7和8,顾不改变排列的逆序数,因此排列的奇偶性不变。
列移动:
1 | 2 | 3 |
---|---|---|
4 | 6 | |
7 | 5 | 8 |
转换为数列:12346758
对于上图上图,列移动有两种情况如下:
1.
1 | 3 | |
---|---|---|
4 | 2 | 6 |
7 | 5 | 8 |
转换为数列:13426758
2.
1 | 2 | 3 |
---|---|---|
4 | 5 | 6 |
7 | 8 |
转换为数列:12345678
对比这三种的数列形式,
原始:12346758
移动1:1 342 6758
移动2:1234 567 8
与原始序列相比,移动1与移动2改变了移动的数字和移动的数字与空白块中间的2个数字。可以看做移动数字与相邻数字连续做了两次交换得到。以移动1为例,数字2先与数字3交换,再与数字4交换。由现代中的定理:数列中任意交换两个数的次序,奇偶性必发生改变。所以两次交换后,实际并不改变数列的奇偶性。
故,列移动并不改变数列的奇偶性。
总上,拼图的移动不改变拼图序列的奇偶性。
3.2 A类拼图与B类拼图不等价
从一开始的表格中可以看到,A类拼图与B类拼图的奇偶性不同,故A类拼图与B类拼图不等价,既无法将A类拼图通过有限次移动转换为B类拼图。
综上,为了实现一个可还原的拼图游戏,我们需要在随机生成时检测其逆序数,防止生成不可还原的拼图。
4.4x4拼图的可还原性
写完上面,发现只适用于奇数x奇数型拼图,所以补个偶数x偶数型拼图的情况。
从思路上讲,这两种情况的可还原性分析是相同的,但具体情况有所不同。如下图,可以发现,纵向交换后,拼图序列的奇偶性改变。
1 | 2 | 3 | 4 |
---|---|---|---|
5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 |
13 | 14 | 15 |
其序列为 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15,
序数为0,是偶序列。
移动12,得到:
1 | 2 | 3 | 4 |
---|---|---|---|
5 | 6 | 7 | 8 |
9 | 10 | 11 | |
13 | 14 | 15 | 12 |
其序列为 1 2 3 4 5 6 7 8 9 10 11 13 14 15 12
序数为3,是奇序列。
这是因为在进行纵向移动时,影响了4个元素,将12 13 14 15变为13 14 15 12,进行了3次交换。故会改变数列的奇偶性。
但是通过某些变形,我们仍可以使用上面3中证明的结论。我们的变形方法即在计算序数前,先将空白块移动到最下面一行在进行计算。因为我们从打乱到还原的过程中空白块向上移动的次数和向下移动的次数必然相等,到最后奇偶性还是没有变化。这样,拼图移动过程中数列的奇偶性改变并不会影响结果。
所以,从另一个角度讲,对于4x4(偶数x偶数)的拼图,只要保证多次移动后空白块的位置与移动前同行,两个序列的奇偶性就不会改变。
另,从拼图游戏可还原性算法分析这篇文章中得到的思路,可以在计算时将逆序数加上(总行数-当前行数)x(总列数-1)也可。不过,只是在拼图生成是进行可还原性检查,可以直接固定最右下角必为空白元素,然后直接计算数列的奇偶性而不用进行任何变换。换句话说,就是随机生成1,2,…,15的排序,最后加上空元素,检查时,只计算前面1至15的排序的奇偶性即可。