拼图游戏 复制粘贴一个叫lemene的人的,这个人是c++博客的用户,我不是,怕以后找不到这篇文章,所以复制粘贴了。文中最后给出了原文链接连接
2011-09-05 21:13
627 查看
本文讨论如何判断拼图游戏中图形是否可以还原。
例1:下图是一个3X3的数字拼图。
图1
它要还原成图2
图2
将问题一般化,在M*N的方格里有M*N-1个不同元素和一个空元素,只有空元素可以与上下左右相邻的元素交换位置。M*N方格中M*N-1个元素和一个空元素的位置确定一个图形。拼图游戏的问题是:一个图形经过一连串的交换能否得到另一个图形,如何得到。从交换方式的可逆性看出这种关系满足等价三性质,如果图形A通过交换变成图形B我们则称它们是等价的。把M*N-1个元素用1至M*N-1编号,空元素编号0。然后展成一个排列。每个图形对应一个排列。确定了展开方式,图形和排列是一一对应的。这里用到的展开方式是行优先的顺序(其他方式展开也能到相应的结果)。将例1的两个图形展开有:图1对应1
3 2 6 0 5 4 7 8,图2对应1
2 3 4 5 6 7 8 0。
定理1:图形A与图形B等价的充要条件图形A的排列的逆序数加上0元素行号和列号的奇偶性等于图形B的排列的逆序数加上0元素行号和列号的奇偶性。为方便表述,把图形排列的逆序数加上0元素行号和列号的奇偶性称为图形的奇偶性。
先看定理1如何起作用,图1:展开的排列1
3 2 6 0 5 4 7 8,它的逆序数为8,0元素行号为2,列号为2。逆序数加行号,列号的奇偶性为偶。图2:展开的排列1
2 3 4 5 6 7 8 0,它的逆序数为8,0元素行号为3,列号为3。逆序数加行号,列号的奇偶性为偶。两个图形的奇偶性相同,根据定理1判断它们等价。
首先证明必要性,即如果图形A与图形B等价,则图形A的奇偶性等于图形B奇偶性。
0元素和某个元素交换位置,则排列的逆序数的奇偶性就改变一次。交换后0元素的行号或者列号会加1或减1,即行号,列号之和的奇偶性也改变一次。这说明拼图的交换方式不改变图形的奇偶性,也说明拼图中至少有两组等价类,奇偶性不同的图形不等价。
下面证明充分性,如果图形A的奇偶性等于图形B的奇偶性,则图形A,B等价。
如果证明了拼图只有两组等价类,从必要性的证明过程可知,奇性图形是一组等价类,偶性是一组。从而证明了充分性。
先考虑一般的排列1 2 3 ... N。某个元素连续与后面M相邻的元素交换位置,称为向后M步移动。如排列:1
2 3 4 5 6。元素2的向后3步移动,排列变成1
3 4 5 2 6。同样的方式定义向前M步移动。如果排列A能够通过有限向前M步移动和向后M步移动变成排列B,称排列A与排列B
M步等价。容易看出这也是等价关系。
引理1:任何一个1至N的排列M步等价于1
2 ... N-M(...)。括号里是N-M+1至N的某个排列。
证明:如果N=M,这显然成立。
假设N=k时成立,下面证明k+1的情况。
1元素的位置记为i
情况1:假设i=1,显然,余下的元素减1,就变成N=k的境况,得证。
情况2:如果1<i<=M,则元素1前面的元素向后M步移动,变为情况1。
情况3:如果i>M,则元素1有限次向前M步移动,使i有1<=i<=M,可变成情况1或2。
从而得证。
当M=2时,只有两组等价类。由于移动不改变排列的奇偶性,从而奇排列是一组等价类,偶排列是一组等价类。
考虑N*M的拼图。
当N=M=2,穷举法可证明只有两组等价类。
当N,M不同时为2时,设N不等于2(如果N等于2,M不等于2可颠倒行列讨论)。
只考虑第二行最后一个元素是空元素的情形,因为空元素在其他位置总可以等价某个空元素在第二行最后一个元素的图形。不考虑空元素以之字形方式展开图形,即第一行最后一个数字和第二行倒数第二个数字相连。如:
图3
展开成1,2,4,5,3。
下面证明两行拼图的交换方式可以实现排列的向前2步和向后2步移动。
要实现元素a的向前2步移动,则可顺着展开的方式循环移动拼图,使a在第一行第二列的位置,使空元素在第二行第二列的位置,此时可把元素i可与空元素对换。然后再沿着展开的顺序还原拼图。
例如:图3的元素4向前2步移动。可以如下操作,
图4
图5
图6
展开成4,1,2,5,3。实现了向前2步移动。
使i在第二行第二列的位置,使空元素在第一行第二列的位置可以实现向后2步移动。根据引理1及,两行拼图可以分成两组等价类。
假设M=k图形可以分成两组等价类,下面证明M=k+1,
只需要证明任何M=k+1图形总等价于第一行元素为1
2 ... N的某图形即可。
如果这N个元素都在第一行,把空元素移到第二行,从上面的证明可知,交换两个不同的非空元素,图形的奇偶性改变,属于不同的等价类。N大于2,第二行就有两个非空元素可供交换。所以两行图形可以等价与第一行为1
2 ... N的某个图形。
如果1至N的某个a元素不在第一行,设它在第i行。把空元素移动到i行,这样第i行和第i-1行可以看成M=2的图形。可以把a移动到第i-1行,并保证第i行和i-1行中1至N的元素的行号不增加。有限步移动可以使1至N元素全部在第一行。
显然M=k+1图形的等价类数目为2。
充分性得证。
拼图游戏的随机离散中加入定理1的判断可以保证游戏有意义,不会出现无解的情况。
原文链接
http://www.cppblog.com/lemene/archive/2007/10/04/33405.aspx
原作者的实现如下
例1:下图是一个3X3的数字拼图。
1 | 3 | 2 |
6 | 5 | |
4 | 7 | 8 |
它要还原成图2
1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 |
将问题一般化,在M*N的方格里有M*N-1个不同元素和一个空元素,只有空元素可以与上下左右相邻的元素交换位置。M*N方格中M*N-1个元素和一个空元素的位置确定一个图形。拼图游戏的问题是:一个图形经过一连串的交换能否得到另一个图形,如何得到。从交换方式的可逆性看出这种关系满足等价三性质,如果图形A通过交换变成图形B我们则称它们是等价的。把M*N-1个元素用1至M*N-1编号,空元素编号0。然后展成一个排列。每个图形对应一个排列。确定了展开方式,图形和排列是一一对应的。这里用到的展开方式是行优先的顺序(其他方式展开也能到相应的结果)。将例1的两个图形展开有:图1对应1
3 2 6 0 5 4 7 8,图2对应1
2 3 4 5 6 7 8 0。
定理1:图形A与图形B等价的充要条件图形A的排列的逆序数加上0元素行号和列号的奇偶性等于图形B的排列的逆序数加上0元素行号和列号的奇偶性。为方便表述,把图形排列的逆序数加上0元素行号和列号的奇偶性称为图形的奇偶性。
先看定理1如何起作用,图1:展开的排列1
3 2 6 0 5 4 7 8,它的逆序数为8,0元素行号为2,列号为2。逆序数加行号,列号的奇偶性为偶。图2:展开的排列1
2 3 4 5 6 7 8 0,它的逆序数为8,0元素行号为3,列号为3。逆序数加行号,列号的奇偶性为偶。两个图形的奇偶性相同,根据定理1判断它们等价。
首先证明必要性,即如果图形A与图形B等价,则图形A的奇偶性等于图形B奇偶性。
0元素和某个元素交换位置,则排列的逆序数的奇偶性就改变一次。交换后0元素的行号或者列号会加1或减1,即行号,列号之和的奇偶性也改变一次。这说明拼图的交换方式不改变图形的奇偶性,也说明拼图中至少有两组等价类,奇偶性不同的图形不等价。
下面证明充分性,如果图形A的奇偶性等于图形B的奇偶性,则图形A,B等价。
如果证明了拼图只有两组等价类,从必要性的证明过程可知,奇性图形是一组等价类,偶性是一组。从而证明了充分性。
先考虑一般的排列1 2 3 ... N。某个元素连续与后面M相邻的元素交换位置,称为向后M步移动。如排列:1
2 3 4 5 6。元素2的向后3步移动,排列变成1
3 4 5 2 6。同样的方式定义向前M步移动。如果排列A能够通过有限向前M步移动和向后M步移动变成排列B,称排列A与排列B
M步等价。容易看出这也是等价关系。
引理1:任何一个1至N的排列M步等价于1
2 ... N-M(...)。括号里是N-M+1至N的某个排列。
证明:如果N=M,这显然成立。
假设N=k时成立,下面证明k+1的情况。
1元素的位置记为i
情况1:假设i=1,显然,余下的元素减1,就变成N=k的境况,得证。
情况2:如果1<i<=M,则元素1前面的元素向后M步移动,变为情况1。
情况3:如果i>M,则元素1有限次向前M步移动,使i有1<=i<=M,可变成情况1或2。
从而得证。
当M=2时,只有两组等价类。由于移动不改变排列的奇偶性,从而奇排列是一组等价类,偶排列是一组等价类。
考虑N*M的拼图。
当N=M=2,穷举法可证明只有两组等价类。
当N,M不同时为2时,设N不等于2(如果N等于2,M不等于2可颠倒行列讨论)。
只考虑第二行最后一个元素是空元素的情形,因为空元素在其他位置总可以等价某个空元素在第二行最后一个元素的图形。不考虑空元素以之字形方式展开图形,即第一行最后一个数字和第二行倒数第二个数字相连。如:
1 | 2 | 4 |
3 | 5 |
展开成1,2,4,5,3。
下面证明两行拼图的交换方式可以实现排列的向前2步和向后2步移动。
要实现元素a的向前2步移动,则可顺着展开的方式循环移动拼图,使a在第一行第二列的位置,使空元素在第二行第二列的位置,此时可把元素i可与空元素对换。然后再沿着展开的顺序还原拼图。
例如:图3的元素4向前2步移动。可以如下操作,
2 | 4 | 5 |
1 | 3 |
2 | 5 | |
1 | 4 | 3 |
4 | 1 | 2 |
3 | 5 |
展开成4,1,2,5,3。实现了向前2步移动。
使i在第二行第二列的位置,使空元素在第一行第二列的位置可以实现向后2步移动。根据引理1及,两行拼图可以分成两组等价类。
假设M=k图形可以分成两组等价类,下面证明M=k+1,
只需要证明任何M=k+1图形总等价于第一行元素为1
2 ... N的某图形即可。
如果这N个元素都在第一行,把空元素移到第二行,从上面的证明可知,交换两个不同的非空元素,图形的奇偶性改变,属于不同的等价类。N大于2,第二行就有两个非空元素可供交换。所以两行图形可以等价与第一行为1
2 ... N的某个图形。
如果1至N的某个a元素不在第一行,设它在第i行。把空元素移动到i行,这样第i行和第i-1行可以看成M=2的图形。可以把a移动到第i-1行,并保证第i行和i-1行中1至N的元素的行号不增加。有限步移动可以使1至N元素全部在第一行。
显然M=k+1图形的等价类数目为2。
充分性得证。
拼图游戏的随机离散中加入定理1的判断可以保证游戏有意义,不会出现无解的情况。
原文链接
http://www.cppblog.com/lemene/archive/2007/10/04/33405.aspx
原作者的实现如下
// puzzle game for window console // gcc puzzle_console.c -o puzzle.exe #include <windows.h> #include <stdio.h> #include <conio.h> #include <stdlib.h> int g_row = 4; // 列数目 int g_line = 4; // 行数目 int g_puzzle[5*7]; // 拼图数据 // clear screen // 清屏 void Clrscr() { HANDLE hConsole; COORD coordScreen = {0, 0}; DWORD cCharsWritten; DWORD dwConSize; CONSOLE_SCREEN_BUFFER_INFO csbi; hConsole=GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo(hConsole, &csbi); dwConSize = csbi.dwSize.X * csbi.dwSize.Y; FillConsoleOutputCharacter(hConsole, (TCHAR)' ', dwConSize, coordScreen, &cCharsWritten); } // void GotoXY(int x, int y) { HANDLE hConsole; COORD pt; pt.X = x; pt.Y = y; hConsole = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleCursorPosition(hConsole, pt); } // 计算拼图的奇偶性 int CalcParity(int *puzzle, int size, int row) { int ret = 0; int i, j; // 计算排列的奇偶性 for (i=0; i<size; i++) { for (j=i+1; j<size; j++) { if (puzzle[i] > puzzle[j]) ret++; } } // 计算0元素位置 for (i=0; i<size; i++) { if (puzzle[i] == 0) break; } ret += i%row + i/row; return ret; } // 拼图是否还原 int IsPuzzleFinished() { int i; for (i=0; i<g_line*g_row-1; i++) { if (g_puzzle[i] != i+1) return 0; } return 1; } // 随机离散拼图 void RandomPuzzle() { srand(GetTickCount()); int size = g_line * g_row; int i = 0; while ( i<size ) { g_puzzle[i] = rand() % size; int j; for (j=0; j<i; j++) { if (g_puzzle[i] == g_puzzle[j]) goto USELESS; } i++; USELESS: ; } // 判断拼图奇偶 if (CalcParity(g_puzzle, size, g_row)%2 != (size-1+g_line-1+g_row-1)%2) { // 如果不相等,交换两个非空元素。 int t, t1, t2; if (g_puzzle[0] == 0) { t1 = 1; t2 = 2; } else if (g_puzzle[1] == 0) { t1 = 0; t2 = 2; } else { t1 = 0; t2 = 1; } t = g_puzzle[t1]; g_puzzle[t1] = g_puzzle[t2]; g_puzzle[t2] = t; } } // 显示主菜单 void PrintMainMenu() { Clrscr(); GotoXY(0, 0); printf("1.Begin game!\n"); printf("2.Adjust line(%d) and row(%d)\n", g_line, g_row); printf("3.Quit\n"); } // 显示拼图 void PrintPuzzle() { HANDLE hConsole; COORD coordScreen = {0, 0}; DWORD cCharsWritten; DWORD dwConSize; CONSOLE_SCREEN_BUFFER_INFO csbi; hConsole=GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo(hConsole, &csbi); dwConSize = csbi.dwSize.X * csbi.dwSize.Y; int i, j; for (i=0; i<=g_line; i++) { for (j=0; j<g_row; j++) { coordScreen.Y = i*2; coordScreen.X = j*4 + 1; FillConsoleOutputCharacter(hConsole, '-', 3, coordScreen, &cCharsWritten); } } for (i=0; i<g_line; i++) { for (j=0; j<=g_row; j++) { coordScreen.Y = i*2+1; coordScreen.X = j*4 ; FillConsoleOutputCharacter(hConsole, '|', 1, coordScreen, &cCharsWritten); } } char str[4]; for (i=0; i<g_line*g_row; i++) { coordScreen.Y = i/g_row*2 + 1; coordScreen.X = i%g_row*4 + 1; if (g_puzzle[i] == 0) { sprintf(str, " "); } else sprintf(str, "%3d", g_puzzle[i]); WriteConsoleOutputCharacter(hConsole, str, 3, coordScreen, &cCharsWritten); } GotoXY(0, g_line*2+2); printf("use %c, %c, %c, %c to move item\n", 0x18, 0x19, 0x1a, 0x1b); printf("'Q' back to menu\n"); } // 调整拼图大小 void AdjustLineAndRow() { int line, row; Clrscr(); GotoXY(0, 0); printf("Please input line(2-5):"); scanf("%d", &line); printf("Please input row(2-7):"); scanf("%d", &row); if (line >=2 && line <=5) g_line = line; if (row >=2 && row <=7) g_row = row; } // 移动拼图 void MovePuzzle(int direct) { int i=0; for (; i<g_line*g_row; i++) if (g_puzzle[i] == 0) break; switch (direct) { case 75: // left; if (i%g_row != g_row-1) { g_puzzle[i] = g_puzzle[i+1]; g_puzzle[i+1] = 0; } break; case 77: // right if (i%g_row != 0) { g_puzzle[i] = g_puzzle[i-1]; g_puzzle[i-1] = 0; } break; case 72: // up if (i < g_line*g_row-g_row) { g_puzzle[i] = g_puzzle[i+g_row]; g_puzzle[i+g_row] = 0; } break; case 80: // down if (i >= g_row) { g_puzzle[i] = g_puzzle[i-g_row]; g_puzzle[i-g_row] = 0; } break; } } // 开始拼图游戏 void BeginGame() { Clrscr(); RandomPuzzle(); PrintPuzzle(); while (1) { int c = getch(); switch (c) { case 'q': case 'Q': return; case 0xe0: MovePuzzle(getch()); break; } PrintPuzzle(); if (IsPuzzleFinished()) { printf("Congratulation,you pass the game!\nAny key back to menu"); getch(); return; } } } int main() { char cInput; while (1) { PrintMainMenu(); cInput = getch(); switch (cInput) { case 'Q': case 'q': case '3': Clrscr(); GotoXY(0,0); goto End; case 'B': case 'b': case '1': BeginGame(); break; case 'A': case 'a': case '2': AdjustLineAndRow(); break; } } End: return 0; }
相关文章推荐
- 介绍一个个人博客中的几篇c++文章
- 浪漫的表白 。刚接触编程语言 ,一个学长做的题。其实很简单,就是找规律输出,只是觉得输出特别浪漫,所以在此作为我人生第一个博客文章!!哈哈,大神之路 走起
- C++ 传值 避免 内存泄漏的一个技巧。[new 了以后,不一定要delete][修正,new后一定delete,没人帮你释放的。我错了,此文章已没任何意义了]
- 较新一篇 / 较旧一篇 编辑 | 删除 | 复制链接 运用ADO方式在C++上连接SQL2008
- 摘要:我们经常会用到递归函数,但是如果递归深度太大时,往往导致栈溢出。而递归深度往往不太容易把握,所以比较安全一点的做法就是:用循环代替递归。文章最后的原文里面讲了如何用10步实现这个过程,相当精彩。本文翻译了这篇文章,并加了自己的一点注释和理解。
- 不是使用C/C++函数如何复制文件到另外一个目录
- 自己做的一个游戏 先不要看程序 想玩的自己复制粘贴运行 不然就没意思了
- 因为想要获取一个免费的mockups license,所以想发布一篇宣传它的文章,但自己写的又不如别人已有的好,所以,不如偷偷懒,转个贴吧!只要能宣传就好,以下是转帖原文
- 随机生成一个0到100的数,让用户猜,允许猜5次,每次猜大了或猜小了,都要给出提示,最后给出正确答案
- 实现复制文章加上原文链接
- C++ - 给出一个函数来连接两个字符串A和B,其中字符串A的后几个字节和字符串B的前几个字节重叠
- 因为老板让我做一个WAP 游戏!所以百分求!WAP版的斗地主游戏 地址!我玩一玩参考一下!希望有玩过的给我一个连接!谢谢
- 一个单用户博客系统DEMO——文章详情中插入评论表单
- 新看到一个C++博客,感觉文章还可以,留个记号
- valueForKeyPath的用法(本篇内容是拷贝别人的文章,因为不能转载,又感觉写的很好,所以复制了,希望作者不要见怪)
- 用有名管道实现文件复制,有两个终端 ,一个进行复制操作,另一个进行粘贴操作
- 当一个用户不是用自己的windowss账户,而是用windows group登陆时,如何查询他的权限?
- vim中如何复制当前文件内容粘贴到另外一个文件的命令
- Linux中的spinlock和mutex 本篇文章来源于 Linux公社网站(www.linuxidc.com) 原文链接:http://www.linuxidc.com/Linux/2011-
- Foxpro数据库连接错误解决方法--【VFP DBF文件不是一个有效的路径。 确定路径名称拼写是否正确,以及是否连接到文件存放的服务器】