赞
踩
【参考1:马踏棋盘问题 — 深搜和贪心算法】(http://blog.csdn.net/u011718609/article/details/60873403)
【参考2:Wiki百科-骑士巡逻】(https://zh.wikipedia.org/wiki/騎士巡邏)
【参考3:Wiki百科-贪心法】(https://zh.wikipedia.org/wiki/贪心法)
【参考4:贴吧某大神对马踏棋盘的见解】(http://tieba.baidu.com/p/2576982396?pn=1)
【参考5:油管Knight’s Tour - Numberphile】(https://www.☺youetuebe☺.com/watch?v=ab_dY3dZFHM)
###1.背景:
已知的最早的骑士巡逻问题可以追溯到九世纪的古印度恰图兰卡。
欧拉是最早研究骑士巡逻的数学家中的一员,而H. C. von Warnsdorff在1823年提出了第一个系统化解决骑士巡逻问题的方法–Warnsdorff规则。
在20世纪,一批乌力波的作家将这个问题用在了其它的地方。最明显的例子:乔治·佩雷克的小说Life: A User’s Manual的章节顺序就是按照10×10棋盘的骑士巡逻路径来编排的。在2010年国际象棋世界冠军对抗赛的第六场比赛中,棋手阿南德连续13次移动骑士(使用了两个骑士),在线评论员打趣地说阿南德试图在游戏过程中解决了骑士巡逻问题。
###2.条件:
马踏棋盘(骑士巡逻)是指在按照中国象棋马的规定走法(国际象棋中骑士的规定走法)走遍整个棋盘的每一个方格,而且每个网格只能够经过一次。马踏棋盘要求是所有位置都要走一遍,而骑士巡逻有两种情况,能够回到最初位置的叫做“封闭巡逻”,不能回到最初位置的称为“开巡逻”,开循环结果与马踏棋盘相同。
马可移动的位置:
一种“开巡逻”情况:一种“封闭巡逻”情况:
###3.解法一 - DFS深度优先搜索:
此方法类似八皇后问题,采用回溯法,就是一遍一遍尝试,如果不行则回溯到上一次,继续进行其他尝试,当然这个算法时间复杂度很高,从Wiki百科知“对于8*8棋盘,一共有26,534,728,821,064种封闭巡逻,但是到底有多少种开巡逻仍然未知”,所以一般情况求出一个解即停止求解过程
#include <iostream> #include <iomanip> #include "stdio.h" #include "string.h" #include "stdlib.h" #include "time.h" using namespace std; int count = 0; void OutputToTxt(int (*chess)[8]) { FILE *fp = fopen("abc.txt","w"); for(int i = 0; i<8; i++) { for(int j = 0; j<8; j++) { fprintf(fp,"%-2d ",chess[i][j]); } fprintf(fp,"\n"); } fclose(fp); } void Horse_Chess(int (*chess)[8], int x, int y, int cnt) { if(count >= 1) return; // 找到1种情况就退出 if(x&0xFFFFFFF8 || y&0xFFFFFFF8) return; // 越界返回 if(chess[x][y] == 0) { chess[x][y] = cnt; // 此位置设置走马的序号 if(cnt >= 64) { OutputToTxt(chess); // 输出到文件 count++; // 可能性+1 return; } /* 以下是8种可能的情况: */ Horse_Chess(chess,x+1,y+2,cnt+1); Horse_Chess(chess,x+2,y+1,cnt+1); Horse_Chess(chess,x+2,y-1,cnt+1); Horse_Chess(chess,x+1,y-2,cnt+1); Horse_Chess(chess,x-1,y-2,cnt+1); Horse_Chess(chess,x-2,y-1,cnt+1); Horse_Chess(chess,x-2,y+1,cnt+1); Horse_Chess(chess,x-1,y+2,cnt+1); chess[x][y] = 0; // 此位置清空,没有走马 } } int main() { clock_t start, end; int chess[8][8]; memset(chess,0,64*sizeof(int)); start = clock(); Horse_Chess(chess,0,0,1); end = clock(); printf("Time:%lfs\n",(double)(end-start)/CLOCKS_PER_SEC); // 打印花费时间 system("pause"); return 0; }
不同电脑硬件花费时间不相同,结果如下:
还要注意的一点就是,改变8种可能的尝试的顺序会极大改变花费时间
###4.解法二 - DFS深度优先搜索 + 贪心法优化:
#include <iostream> #include <iomanip> #include "stdio.h" #include "string.h" #include "stdlib.h" #include "time.h" using namespace std; #define IS_WITH_LIMITS(x,y) ((((x)&0xFFFFFFF8) == 0) &&(((y)&0xFFFFFF8) == 0)) // 判断是否越界 int count = 0; void OutputToTxt(int (*chess)[8]) { FILE *fp = fopen("abc.txt","w"); for(int i = 0; i<8; i++) { for(int j = 0; j<8; j++) { fprintf(fp,"%-2d ",chess[i][j]); } fprintf(fp,"\n"); } fclose(fp); } const int step[][2] = {{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,-1},{-2,1},{-1,2}}; // 8种走法x y变化的常量数组 int Count_Step(int (*chess)[8], int x, int y) // 计算位置(x,y)走法个数函数 { if(!IS_WITH_LIMITS(x,y) || chess[x][y] != 0) return 8; // 此位置是否越界或是否走过了,是则返回8,用8因为不可能某个点还有8种走法,而且用8便于判断最小值 int cnt = 0; for(int i = 0; i<8; i++) { if(IS_WITH_LIMITS(x+step[i][0],y+step[i][1]) && chess[x+step[i][0]][y+step[i][1]] == 0) cnt++; // 判断此位置有几个可能走法 } return cnt; } void Horse_Chess_Greedy(int (*chess)[8], int x, int y, int cnt) { if(count >= 1) return; if(!IS_WITH_LIMITS(x,y)) return; // 越界返回 if(chess[x][y] == 0) { chess[x][y] = cnt; // 此位置设置走马的序号 if(cnt >= 64) { OutputToTxt(chess); // 输出到文件 count++; // 可能性+1 return; } /* 以下是通过贪心法计算的可能的情况: */ int stepCnt[8]; for(int i = 0; i<8; i++) stepCnt[i] = Count_Step(chess,x+step[i][0],y+step[i][1]); // 此位置下8种位置的走法个数 /* 理论上贪心法只判断最优情况,所以不需要尝试所有8个位置,这里还是加上了8种走法 */ bool flag = false; for(int i = 0; i<8; i++) { int min = 9,next = 0; for(int j = 0; j<8; j++) { if(min > stepCnt[j]) // 判断最小,这里类似冒泡排序和选择排序 { flag = true; min = stepCnt[j]; next = j; } } if(flag) { Horse_Chess_Greedy(chess,x+step[next][0],y+step[next][1],cnt+1); // 选择走法最少的位置进行下一趟递归 stepCnt[next] = 8; // 这个地方走过了,则设置为8,继续寻找次优解 flag = false; }else{ break; } } chess[x][y] = 0; // 此位置清空,没有走马 } } int main() { clock_t start, end; int chess[8][8]; memset(chess,0,64*sizeof(int)); start = clock(); Horse_Chess_Greedy(chess,0,0,1); end = clock(); printf("Process Time:%lfs\n",(double)(end-start)/CLOCKS_PER_SEC); // 打印花费时间 system("pause"); return 0; }
代码运行结果及分步演示(代码没给):
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。