【跳马问题】“回溯法”——《算法设计与分析(第五版)》

文章目录


一、算法要求

问题描述:在 N*N 棋盘上有 N^2个格子,马在初始位置(X0,Y0),按照象棋中马走“日” 的规则,
使马走遍全部格子且每个格子仅经过一次。编程输出马的走法。
编程实现,给出 N=5,(X0,Y0)=(1,1)时的运行结果。

1. 思路

有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。
回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。
回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。
算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。


二、完整代码

1. 主文件

main.cpp:

// Project4_2: 用回溯法求解跳马问题

#include"Improve2.h"

int main() {

	//初始数据
	const int lengthBoard = 5,
		begingX = 1,
		begingY = 1;

	//打印初始数据
	cout << "#The initial data are as follows: \n"
		<< "\tLength of the chessboard: " << lengthBoard << endl
		<< "\tInitial coordinates: ( " << begingX << " , " << begingY << " )" << endl;

	Console();
	// 调用回溯函数 
	Backtracking(auxiliaryX, auxiliaryY);						
	// 打印方法数量
	cout << "#The number of possible methods in total is: "
		<< countMethod << endl;

	return 0;
}


2. 头文件

Improve2.h:

#pragma once
#ifndef __IMPROVE2__
#define __IMPROVE2__

#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;

//初始数据
const int lengthBoard = 5,
	begingX = 1,
	begingY = 1;


int countStep = 0, 			// 标记第几步
	countMethod = 1;		// 标记这是第几种可能的输出 
const int auxiliaryBoard = lengthBoard + 4,
	auxiliaryX = begingX + 1,
	auxiliaryY = begingY + 1;

int meansMove[8][2],		// 保存八种走法
	chessBoard[auxiliaryBoard][auxiliaryBoard];		// 棋盘 

void Console() {

	//初始化棋盘
	int i, j;
	for (i = 0; i < 2; i++) {//up
		for (j = 0; j < auxiliaryBoard; j++) {
			chessBoard[i][j] = -1;
		}
	}
	for (i = auxiliaryBoard - 2; i < auxiliaryBoard; i++) {//down
		for (j = 0; j < auxiliaryBoard; j++) {
			chessBoard[i][j] = -1;
		}
	}
	for (i = 0; i < auxiliaryBoard; i++) {//left
		for (j = 0; j < 2; j++) {
			chessBoard[i][j] = -1;
		}
	}
	for (i = 0; i < auxiliaryBoard; i++) {//right
		for (j = auxiliaryBoard - 2; j < auxiliaryBoard; j++) {
			chessBoard[i][j] = -1;
		}
	}
	for (i = 2; i < auxiliaryBoard - 2; i++) {//inside
		for (j = 2; j < auxiliaryBoard - 2; j++) {
			chessBoard[i][j] = 0;
		}
	}

	//初始化八种走法meansMove[x][y]
	meansMove[0][0] = 2; 	meansMove[0][1] = 1;		// {x+2,y+1}
	meansMove[1][0] = 1;	meansMove[1][1] = 2;		// {x+1,y+2}
	meansMove[2][0] = -1;	meansMove[2][1] = 2;		// {x-1,y+2}
	meansMove[3][0] = -2;	meansMove[3][1] = 1;		// {x-2,y+1}
	meansMove[4][0] = -2;	meansMove[4][1] = -1;		// {x-2,y-1}
	meansMove[5][0] = -1;	meansMove[5][1] = -2;		// {x-1,y-2}
	meansMove[6][0] = 1;  	meansMove[6][1] = -2;		// {x+1,y-2}	
	meansMove[7][0] = 2;    meansMove[7][1] = -1;		// {x+2,y-1}

	chessBoard[auxiliaryX][auxiliaryY] = ++countStep;	// 初始步数 
	
}

// 打印棋盘 
void Display() {
	cout << "\n#The following is a simple situation: " << endl;
	for (int i = 2; i < auxiliaryBoard - 2; i++) {
		for (int n = 0; n < lengthBoard; n++)
			cout << "+--";
		cout << "+" << endl;

		for (int j = 2; j < auxiliaryBoard - 2; j++) {
			if (chessBoard[i][j] < auxiliaryBoard - 2)
				cout << "|" << setw(2) << chessBoard[i][j] ;
			else
				cout << "|" << setw(2) << chessBoard[i][j] ;
		}
		cout << "|" << endl;
	}
	for (int m = 0; m < lengthBoard; m++)
		cout << "+--";
	cout << "+\n" << endl;
}

// 跳马回溯函数
void Backtracking(int x, int y) {
	if (countStep == (lengthBoard*lengthBoard)) {//终止条件
		countMethod++;
		if (countMethod == 2) {
			Display();
		}
	}

	int i;
	for (i = 0; i < 8; i++) {						// 8种可能跑法 
		// 计算准备要走的这一步的位置
		int a = x + meansMove[i][0];
		int b = y + meansMove[i][1];
		if (chessBoard[a][b] == 0) {				// 能走 
			chessBoard[a][b] = ++countStep;			// 标记 
			Backtracking(a, b);						// 向下走 
			chessBoard[a][b] = 0;					// 退回来,还原状态 
			countStep--; 							// 对称处理 
		}
	}
}

#endif


3. 效果展示

【跳马问题】“回溯法”——《算法设计与分析(第五版)》


三、补充

回溯算法效率
问题的解向量:回溯法希望一个问题的解能够表示成一个n元式(x1,x2,…,xn)的形式。
显约束:对分量xi的取值限定。
隐约束:为满足问题的解而对不同分量之间施加的约束。
解空间:对于问题的一个实例,解向量满足显式约束条件的所有多元组,构成了该实例的一个解空间。

注意:同一个问题可以有多种表示,有些表示方法更简单,所需表示的状态空间更小(存储量少,搜索方法简单)。

文档供本人学习笔记使用,仅供参考。

上一篇:Qt6 QML Book/图形效果/小结


下一篇:利用深度优先搜索等算法实现围棋棋盘控制