by.Qin3Yu
请注意,阅读本文需要您先掌握顺序表的基本操作,具体可参阅我的往期博客:
【C++数据结构 | 顺序表速通】使用顺序表完成简单的成绩管理系统.by.Qin3Yu
本文所使用搜索方法实质为深度优先搜索(DFS),相关内容可参阅我的往期博客:
【算法详解 | DFS算法】深度优先搜索解走迷宫问题 | 深度优先图遍历.by.Qin3Yu
文中所有代码使用 C++ 举例,且默认已使用 std 命名空间:
using namespace std;
针对本文中的部分代码案例,还需要额外导入以下头文件:
#include
// 定义棋盘大小为8
#define N 8
...
// 定义棋盘规格为8x8,且初始化为未访问(-1)
vector<vector<int>> board(N, vector<int>(N, -1));
// 用pair定义骑士的移动路径,即(x,y)
vector<pair<int, int>> path(N * N);
int dx[8] = { -2, -1, 1, 2, 2, 1, -1, -2 };
int dy[8] = { 1, 2, 2, 1, -1, -2, -2, -1 };
bool isSafe(const vector<vector<int>>& board, int x, int y) {
return (x >= 0 && x < N && y >= 0 && y < N && board[x][y] == -1);
}
// 设置起始位置(0,0)
int startX = 0;
int startY = 0;
// 将起始位置标记为已访问
board[startX][startY] = 0;
//向路径中添加一个坐标pair
path[0] = make_pair(startX, startY);
// 为方便阅读写为两行
bool solveKnightTour(vector<vector<int>>& board,
vector<pair<int, int>>& path, int x, int y, int moveNum) {}
在算法中,我们依次尝试往骑士的八个可移动方向移动,如果可以移动,则移动后再次调用函数(递归),直到移动的步数 moveNum 等于棋盘的格子数 N × N ,则说明骑士已经走遍了整个棋盘,返回 true 。
如果8个方向均已被尝试如果8个方向均已被尝试过,那么说明当前路径不能找到一条成功的路径,需要进行回溯操作。回溯操作会将当前位置标记为未访问,然后进入下一个循环,尝试下一个可能的移动方向。
具体来说,在这段代码中,如果所有的八个方向都被尝试过后,递归调用 solveKnightTour 函数没有返回 true ,那么程序会执行下面的回溯操作:
- 将当前位置 (nextX, nextY) 标记为未访问,即 board[nextX][nextY] = -1 。
- 继续循环,尝试下一个移动方向,即下一个 k 值。
- 如果所有的方向都被尝试过并且没有找到成功的路径,那么最终 solveKnightTour 函数会返回 false 。
// 如果所有方格都已访问,则问题解决
if (moveNum == N * N)
return true;
// 尝试骑士的所有移动方向
for (int k = 0; k < 8; ++k) {
int nextX = x + dx[k];
int nextY = y + dy[k];
if (isSafe(board, nextX, nextY)) {
board[nextX][nextY] = moveNum; // 标记该位置为已访问
path[moveNum] = make_pair(nextX, nextY); // 记录路径
if (solveKnightTour(board, path, nextX, nextY, moveNum + 1))
return true;
board[nextX][nextY] = -1; // 回溯,将该位置标记为未访问
}
}
return false;
// 打印解决方案
void printSolution(const vector<pair<int, int>>& path) {
for (int i = 0; i < N * N; ++i) {
// 打印出pair中的元素
cout << "(" << path[i].first << ", " << path[i].second << ") ";
// 每打印几个便换一行,方便观察
if ((i + 1) % N == 0)
cout << endl;
}
}
int main() {
......
// 解决骑士之旅问题
if (solveKnightTour(board, path, startX, startY, 1)) {
cout << "存在解决方案:" << endl;
// 调用函数打印
printSolution(path);
}
else
cout << "不存在解决方案。" << endl;
}
参考代码(以8x8棋盘为例):
#include
#include
using namespace std;
// 定义棋盘大小
#define N 8
// 定义骑士的移动方向
int dx[8] = { -2, -1, 1, 2, 2, 1, -1, -2 };
int dy[8] = { 1, 2, 2, 1, -1, -2, -2, -1 };
// 打印解决方案
void printSolution(const vector<pair<int, int>>& path) {
for (int i = 0; i < N * N; ++i) {
cout << "(" << path[i].first << ", " << path[i].second << ") ";
if ((i + 1) % N == 0)
cout << endl;
}
}
// 检查位置是否在棋盘内且尚未访问过
bool isSafe(const vector<vector<int>>& board, int x, int y) {
return (x >= 0 && x < N && y >= 0 && y < N && board[x][y] == -1);
}
// 使用回溯递归解决骑士之旅问题
bool solveKnightTour(vector<vector<int>>& board, vector<pair<int, int>>& path, int x, int y, int moveNum) {
// 如果所有方格都已访问,则问题解决
if (moveNum == N * N)
return true;
// 尝试骑士的所有移动方向
for (int k = 0; k < 8; ++k) {
int nextX = x + dx[k];
int nextY = y + dy[k];
if (isSafe(board, nextX, nextY)) {
board[nextX][nextY] = moveNum; // 标记该位置为已访问
path[moveNum] = make_pair(nextX, nextY); // 记录路径
if (solveKnightTour(board, path, nextX, nextY, moveNum + 1))
return true;
board[nextX][nextY] = -1; // 回溯,将该位置标记为未访问
}
}
return false;
}
int main() {
// 初始化棋盘和路径
vector<vector<int>> board(N, vector<int>(N, -1));
vector<pair<int, int>> path(N * N);
// 设置起始位置
int startX = 0;
int startY = 0;
// 将起始位置标记为已访问
board[startX][startY] = 0;
path[0] = make_pair(startX, startY);
// 解决骑士之旅问题
if (solveKnightTour(board, path, startX, startY, 1)) {
cout << "存在解决方案:" << endl;
printSolution(path);
}
else
cout << "不存在解决方案。" << endl;
system("pause");
return 0;
}
参考输出:
存在解决方案:
(0, 0) (1, 2) (0, 4) (1, 6) (3, 7) (5, 6) (7, 7) (6, 5)
(4, 6) (2, 7) (3, 5) (4, 7) (6, 6) (7, 4) (5, 5) (3, 6)
(1, 7) (2, 5) (0, 6) (1, 4) (2, 6) (0, 7) (1, 5) (3, 4)
(5, 3) (4, 5) (5, 7) (7, 6) (6, 4) (7, 2) (6, 0) (4, 1)
(2, 2) (0, 3) (2, 4) (0, 5) (1, 3) (0, 1) (2, 0) (3, 2)
(4, 4) (6, 3) (7, 1) (5, 0) (4, 2) (2, 3) (1, 1) (3, 0)
(5, 1) (7, 0) (6, 2) (4, 3) (3, 1) (1, 0) (0, 2) (2, 1)
(3, 3) (5, 2) (4, 0) (6, 1) (7, 3) (5, 4) (7, 5) (6, 7)