骑士遍历问题(回溯法)

参考

link

概述

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

思路

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

code

cpp

不知道为啥输出不了正确答案。
一直找不出bug。
受大佬上次指点思路的启发。我觉得,这次先改动改动,把棋盘变小,把功能拆分,慢慢去debug。这样debug容易一些。
还有就是,debug的时候,用的print可以输出更清楚一些的说明,这样就能看懂了。
出错代码

#include<stdio.h>
#include<iostream>
#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<<"尝试第"<<num<<"层循环"<<"第"<<i<<"个方向"<<endl; 
		//如果i方向能走 
		if(cut(x,y,i,map)){
			//cout<<"第"<<num<<"步"<<"从"<<x - 1<<" "<<y - 1<<"向"<<i<<"方向走"<<endl; 
			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是一样的。

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回溯马步遍问题的复杂度分析需要考虑以下几个因素: 1. 棋盘的大小:设棋盘大小为 $n \times m$,则遍问题的规模为 $nm$。 2. 起始位置的选择:由于马的走具有对称性,因此可以假设起始位置为 $(0, 0)$,并对其它起始位置进行对称处理。这样可以减少起始位置的选择,但并不会影响问题的复杂度。 3. 马的走:每个格子可以选择的下一个格子最多有 $8$ 个,因此每个格子最多需要尝试 $8$ 次才能找到一个可行的下一个格子。 4. 剪枝优化:在实际的实现中,可以使用一些剪枝优化来减少回溯的次数,例如可以按照下一个格子能够到达的未访问过的格子数量从小到大进行排序,这样可以优先选择能够到达较少未访问过的格子的下一个格子。 根据以上因素,回溯马步遍问题的复杂度可以分析如下: - 时间复杂度:设棋盘大小为 $n \times m$,则回溯马步遍问题的时间复杂度为 $O(8^{nm})$,因为每个格子最多需要尝试 $8$ 次才能找到一个可行的下一个格子,而遍所有格子需要尝试的次数是指数级别的。 - 空间复杂度:回溯马步遍问题的空间复杂度取决于递归调用栈的深度,即最多需要保存 $nm$ 个格子的信息。因此空间复杂度为 $O(nm)$。 需要注意的是,由于回溯马步遍问题的时间复杂度是指数级别的,因此对于较大的棋盘来说,回溯是无在合理的时间内得到解决的。在实际应用中,需要考虑使用其它更加高效的算来解决马步遍问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值