骑士遍历问题(回溯法)

文章目录

  • 参考
  • 概述
  • 思路
  • code
    • cpp
      • 验证输出结果是否正确
    • python

参考

link

概述

骑士游历问题是放在n*n的国际象棋棋盘上的一个马,按照马走"日"字的规则是否能够不重复地走遍棋盘的每个格。

思路

骑士最后要遍历所有的点。每次运动最多有八种方式。已经去过的点不会再去。
解空间:每次有八种方式走到下一个点(剪枝:剪掉去过的点和没法到达的点)没走过的格子标0,走过的标1.为了方便起见,边界直接标1。
然后试着在地图外标了一圈1,发现自己太天真了,应该标两圈1。但是这样不是更麻烦吗
回溯法有两种思路:1.走不通回退。这样只能找到一种解法。2.遍历所有的,走不通就不走了,只考虑能走通的。这样可以找到所有解法。

code

cpp

不知道为啥输出不了正确答案。
一直找不出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来写一下。这里数组复制什么的,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是一样的。

你可能感兴趣的:(数据结构)