link
骑士游历问题是放在n*n的国际象棋棋盘上的一个马,按照马走"日"字的规则是否能够不重复地走遍棋盘的每个格。
骑士最后要遍历所有的点。每次运动最多有八种方式。已经去过的点不会再去。
解空间:每次有八种方式走到下一个点(剪枝:剪掉去过的点和没法到达的点)没走过的格子标0,走过的标1.为了方便起见,边界直接标1。
然后试着在地图外标了一圈1,发现自己太天真了,应该标两圈1。但是这样不是更麻烦吗
回溯法有两种思路:1.走不通回退。这样只能找到一种解法。2.遍历所有的,走不通就不走了,只考虑能走通的。这样可以找到所有解法。
不知道为啥输出不了正确答案。
一直找不出bug。
受大佬上次指点思路的启发。我觉得,这次先改动改动,把棋盘变小,把功能拆分,慢慢去debug。这样debug容易一些。
还有就是,debug的时候,用的print可以输出更清楚一些的说明,这样就能看懂了。
出错代码
#include
#include
#define SIZE 3
using namespace std;
void travel(int x, int y, int num, int route[], int map[][SIZE + 4]);//递归函数,在当前(x,y)坐标下的走法,确定route[num]值
bool cut(int x, int y, int i, int map[][SIZE + 4]);//剪枝函数,在(x,y)坐标下能否向i方向走
void print(int route[]);//打印路线
int result = 1;
int direction[8][2] = {
{2,1}, {1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}
};//八个方向
int main()
{
int map[SIZE + 4][SIZE + 4] = {0};
int route[SIZE * SIZE] = {0}; //方向数组
int i,j;
//地图初始化
for(j = 0; j < SIZE + 4; j++){
map[0][j] = 1;
map[1][j] = 1;
map[SIZE + 2][j] = 1;
map[SIZE + 3][j] = 1;
}
for(i = 0; i < SIZE + 2; i++){
map[i][0] = 1;
map[i][1] = 1;
map[i][SIZE + 2] = 1;
map[i][SIZE + 3] = 1;
}
int x,y;
cin>>x>>y;//输入起始横纵坐标
x++;
y++;
map[x][y] = 1;
travel(x, y, 1, route, map);//在当前坐标下开始走
return 0;
}
void travel(int x, int y, int num, int route[], int map[][SIZE + 4]){
int i;
//返回条件,当走完所有格子
if(num == SIZE * SIZE - 1){
print(route);
return;
}
//遍历每一种方向
for( i = 0; i < 8; i++){
cout<<"*"<<endl;
//如果i方向能走
if(cut(x,y,i,map)){
cout<<"第"<<num<<"步"<<"从"<<x - 1<<" "<<y - 1<<"向"<<i<<"方向走"<<endl;
route[num] = i;
x += direction[i][0];
y += direction[i][1];
map[x][y] = 1;
num++;
travel(x, y, num, route, map);
}
}
//如果没有办法走
if(i == 8){
cout<<"走到"<<num<<"的时候不通"<<endl;
return;
}
}
bool cut(int x, int y, int i, int map[][SIZE + 4]){
x += direction[i][0];
y += direction[i][1];
if(map[x][y] == 1){
return false;
}
else{
return true;
}
}
void print(int route[]){
int i;
cout<<"第"<<result<<"组:";
for(i = 1; i < SIZE * SIZE; i++){
cout<<route[i]<<" ";
}
cout<<endl;
}
注意这里
travel(x, y, num, route, map);
这样的话,因为route和map是地址,所以它们的值已经改变了。就不能进行下一步运算了。而且map1的值也改变了。所以在修改map1并进行下一步循环之后,需要把map1修改回来。这个问题,其实挺简单的吧,但是我找了好几天才找出来orz。
解决方法有两种。1.想办法吧route和map复制一份,再把x1,x2传入,不影响x,y,map1赋值之后再修改过来。这样就不影响原来的。2.回退。
修改后代码如下。
for( i = 0; i < 8; i++){
//cout<<"尝试第"<
//如果i方向能走
if(cut(x,y,i,map)){
//cout<<"第"<
route1[num] = i;
x1 = x + direction[i][0]; //不改动x,y
y1 = y + direction[i][1];
map1[x1][y1] = 1;
travel(x1, y1, num + 1, route1, map1); //当然得把改动后的传入
//print_map(map);
map1[x1][y1] = 0; //重新赋值为0
}
}
然后就能跑出来了。试了下55棋盘的,结果有300多种。66的,程序跑了有十几分钟吧,还没完。写到这里的时候有三万多种了。如果不剪枝的话,有8^36=3.2451855365843e+32
种路径。
这么多解法是否都是真实可行的呢?找几个验证也好麻烦啊QAQ。写个程序验证好了。
顺便去学习了一下python文件的基本方法。
首先我把所有路线都打印在了地图上。看看走的有没有问题。
然后另外一件事情是,我把所有的路线存在了一个数组中,用
if len(routes) != len(set(routes)):
print("有重复的")
来判断这些路线是否有重复
import io
import sys
#改变标准输出的默认编码
sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='utf8')
SIZE = 6
f = open('测试结果2.txt', 'r', encoding='UTF-8')
f2 = open("输出结果.txt", 'a')
a = f.readlines() # 提取文件中的每一行,返回每一行,这是一个列表
direction = [[1,2],[2,1],[2,-1],[1,-2],[-1,-2],[-2,-1],[-2,1],[-1,2]]
routes = []
def paint(new_line):
f2.write("***********\n")
# 初始化坐标
x = 0
y = 0
num = 1
map = [[0 for i in range(SIZE)] for i in range(SIZE)] # 初始化地图
map[x][y] = num
num += 1
# 确定第几步走到哪里
for char in new_line:
# 如果是数字(方向)的话
if char.isdigit():
char = int(char)
# 改变x,y方向
x += direction[char][0]
y += direction[char][1]
map[x][y] = num
num += 1
'''
这种方法打印出来的没有对齐,不太方便看
for i in range(SIZE):
f2.write(" ".join(str(v) for v in map[i]))
f2.write('\n')
'''
# 打印地图,地图上的数字i表示第i步走到这里
for i in range(SIZE):
for j in range(SIZE):
if map[i][j] < 10:
f2.write("0" + str(map[i][j]) + " ")
else:
f2.write(str(map[i][j]) + " ")
f2.write('\n')
for line in a:
start = line.find(":") + 1
new_line = line[start:] # 截取后面的结果部分
routes.append(new_line) # 把路线放在routes数组中
paint(new_line) #根据对应步骤在文件中画出地图
if len(routes) != len(set(routes)):
print("有重复的")
else:
print('没有重复')
从输出结果的文件来看,应当是没有问题
一时心血来潮,想着用python来写一下。这里数组复制什么的,python更为方便。
import io
import sys
#改变标准输出的默认编码
sys.stdout=io.TextIOWrapper(sys.stdout.buffer, encoding='utf8')
# 方向
direction = [[1,2],[2,1],[2,-1],[1,-2],[-1,-2],[-2,-1],[-2,1],[-1,2]]
total = 1
SIZE = 5
result_num = 1
# 打印路线
def print_route(route):
global result_num
print("No" + str(result_num) + ".:", end = '')
result_num += 1
for num in route:
print(str(num) + " ", end = '')
print("\n")
# 打印地图
def print_map(map):
for i in range(SIZE + 4):
for j in range(SIZE + 4):
print(str(map[i][j]) + " ", end = '')
print("\n")
#在x,y坐标下继续走第num+1个格子,传入当前的route,map
def travel(x, y, num, route, map):
# 如果走完了 SIZE*SIZE - 1步
if num == SIZE * SIZE:
print("#################################")
print_route(route) #
return
# 复制route, map
route1 = route.copy()
map1 = [row[:] for row in map]
for i in range(8):
#print("第" + str(num) + "步尝试向" + str(i) + "方向走" )
# 向i方向走到x1,y1
x1 = x + direction[i][0]
y1 = y + direction[i][1]
# 如果能走通的话
if(map1[x1][y1] != 1):
#print("第" + str(num) + "步" + "从" + str(x - 1) + "," + str(y - 1) + "向" + str(i) + "方向走")
map1[x1][y1] = 1
#print_map(map1)
route1[num] = i
travel(x1, y1, num + 1, route1, map1) # 走第num + 1步
# 不影响下一步
map1[x1][y1] = 0
route1[num] = 0
# 初始化地图
map = [[0 for i in range(SIZE + 4)] for i in range(SIZE + 4)]
# 边界为1
for i in range(SIZE + 4):
map[0][i] = 1
map[1][i] = 1
map[SIZE + 2][i] = 1
map[SIZE + 3][i] = 1
map[i][0] = 1
map[i][1] = 1
map[i][SIZE + 2] = 1
map[i][SIZE + 3] = 1
x = input("请输入横坐标")
y = input("请输入纵坐标")
x = int(x) + 1
y = int(y) + 1
map[x ][y] = 1 # 第一个走过的格子
num = 1
route = [0] * SIZE * SIZE
travel(x, y, num, route, map)
print("end")
跑出来结果和cpp是一样的。