- 骑士巡游是指在棋盘上,骑士的一系列移动,使得骑士恰好访问棋盘上的每一个方格一次。如果骑士最终停在一个与起始方格相隔一个 “马步” (象棋)的方格上(这样它就可以立即沿着相同的路径再次巡游棋盘),那么这个周游是 “闭合的”;否则,它就是 “开放的”。
- 下面进入代码部分讲解:
1. 头文件和命名空间
#include <array>
#include <iostream>
namespace backtracking {
namespace knight_tour {
// 函数定义
}
}
-
头文件:
<array>
用于数组操作,<iostream>
用于输入输出。 -
命名空间:将代码逻辑组织到
backtracking::knight_tour
中,提高模块化。
2. 安全检查函数 issafe
template <size_t V>
bool issafe(int x, int y, const std::array<std::array<int, V>, V>& sol) {
return (x < V && x >= 0 && y < V && y >= 0 && sol[x][y] == -1);
}
-
功能:检查坐标
(x, y)
是否在棋盘范围内且未被访问过。 -
参数:
-
V
:棋盘大小(如8x8)。 -
x, y
:待检查的位置。 -
sol
:棋盘状态,-1
表示未访问。
-
-
返回值:合法返回
true
,否则false
。
3. 回溯求解函数 solve
template <size_t V>
bool solve(int x, int y, int mov, std::array<std::array<int, V>, V>& sol,
const std::array<int, V>& xmov, std::array<int, V>& ymov) {
// 终止条件:所有格子已访问
if (mov == V * V) return true;
// 尝试所有8种移动方向
for (int k = 0; k < V; k++) {
int xnext = x + xmov[k];
int ynext = y + ymov[k];
if (issafe<V>(xnext, ynext, sol)) {
sol[xnext][ynext] = mov; // 标记当前位置
// 递归尝试下一步
if (solve<V>(xnext, ynext, mov + 1, sol, xmov, ymov)) {
return true; // 找到解,提前返回
} else {
sol[xnext][ynext] = -1; // 回溯,撤销当前标记
}
}
}
return false; // 所有方向均无解
}
-
功能:递归尝试所有可能的移动路径,找到骑士周游问题的解。
-
参数:
-
x, y
:当前位置。 -
mov
:当前步数(从0开始)。 -
sol
:棋盘状态,记录每一步的位置。 -
xmov, ymov
:骑士的8种移动方向。
-
-
流程:
-
终止条件:当
mov
等于棋盘格子总数时,所有格子已访问,返回true
。 -
遍历所有移动方向,计算下一步坐标
(xnext, ynext)
。 -
安全检查:若下一步合法,则标记该位置为当前步数。
-
递归探索:进入下一步(
mov + 1
),若递归返回true
,表示找到解,逐层返回。 -
回溯:若递归未找到解,撤销当前标记,尝试其他方向。
-
无解:所有方向尝试完毕仍无解,返回
false
。
-
4. 主函数 main
int main() {
const int n = 8;
std::array<std::array<int, n>, n> sol;
// 初始化棋盘为-1(未访问)
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
sol[i][j] = -1;
}
}
// 定义骑士的8种移动方向(相对坐标)
std::array<int, n> xmov = {2, 1, -1, -2, -2, -1, 1, 2};
std::array<int, n> ymov = {1, 2, 2, 1, -1, -2, -2, -1};
sol[0][0] = 0; // 起始位置
// 调用求解函数
bool flag = backtracking::knight_tour::solve<n>(0, 0, 1, sol, xmov, ymov);
// 输出结果
if (!flag) {
std::cout << "Solution does not exist!\n";
} else {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
std::cout << sol[i][j] << "\t";
}
std::cout << "\n";
}
}
return 0;
}
-
流程:
-
初始化棋盘:所有位置设为
-1
,表示未访问。 -
定义移动方向:骑士的8种可能移动(如
(2, 1)
表示横向移动2格,纵向1格)。 -
设置起始点:
sol[0][0] = 0
表示从左上角开始。 -
调用求解函数:从
(0,0)
开始,步数为1。 -
输出结果:若找到解,打印棋盘每一步的位置;否则提示无解。
-
5. 算法分析
-
回溯策略:通过递归尝试所有可能的路径,遇到死胡同时回溯,撤销最后一步并尝试其他方向。
-
时间复杂度:最坏情况下为指数级(O(8^(N^2))),但实际中通过剪枝可大幅优化。
-
空间复杂度:O(N^2) 用于存储棋盘状态。
6. 示例输出
对于8x8棋盘,输出为一个8x8矩阵,每个元素表示骑士在第几步访问该位置。例如:
0 59 38 33 30 17 8 63
37 34 31 60 9 62 29 16
58 1 36 39 32 27 18 7
35 48 41 26 61 10 15 28
42 57 2 49 40 23 6 19
47 50 45 54 25 20 11 14
56 43 52 3 22 13 24 5
51 46 55 44 53 4 21 12
-
验证:每个数字从0到63连续,相邻数字间符合骑士移动规则。
总结
该代码通过回溯法系统地探索所有可能的骑士路径,确保找到解(如果存在)。虽然时间复杂度较高,但对于标准8x8棋盘仍能有效求解。