关于棋盘马走“日”字问题的回溯算法实现
2010-12-14 17:56
429 查看
1. 回溯法基本思想
回溯法是在包含问题的所有解得解空间树(或森林)中,按照深度优先的策略,从根结点出发搜索解空间树。
算法搜索至解空间树的任一结点时,总是先判断该结点是否满足问题的约束条件。如果满足进入该子树,继续
按照深度优先的策略进行搜索。否则,不去搜索以该结点为根的子树,而是逐层向其祖先结点回溯。
回溯法在用来求解问题的所有解时,要回溯到根,且根结点的所有可行的子树都已被搜索遍才结束。而回溯法
在用来求解问题的任一解时,只要搜索到问题的一个解就可以结束。适用于解决一些最优化问题。
2. 算法设计过程
(1) 确定问题的解空间
应用回溯法解决问题时,首先应明确定义问题的解空间。问题的解空间应至少包含问题的一个最优解。
(2) 确定结点的扩展规则
约束条件。
(3) 搜索解空间
回溯算法从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也
成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并
成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应该往回
移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中
搜索,直至找到所要求的解或解空间中已没有活结点时为止。
3. 算法框架
(1) 问题框架
设问题的解是一个n维向量(a1,a2,...,an),约束条件是ai(i1,2,...,n)之间满足某种条件,记为f(ai)。
(2) 非递归回溯框架
int a
, i;
i=1;
while(i>0(有路可走) and [未达到目标]){ //还未回溯到头
if(i>n){ //搜索到叶结点
搜索到一个解,输出;
}else{
a[i]第一个可能的值;
while(a[i]不满足约束条件且在搜索空间内)
a[i]下一可能的值;
if(a[i]在搜索空间内){
标识占用的资源;
i = i+1; //扩展下一个结点
}else{
清理所占的状态空间;
i = i-1; //回溯
}
}
}
(3)递归算法框架
int a
;
try(int i){
if(i>n){
输出结果;
}else{
for(j=下界; j<=上界; j++){//枚举i所有可能的路径
if(f(j)){ //满足限界函数和约束条件
a[i] = j;
... //其他操作
try(i+1);
a[i] = 0; //回溯前的清理工作(如a[i]置空)
}
}
}
}
4. 一个例子
(1) 问题描述
马的遍历问题。
在n*m的棋盘中,马只能走"日"字。马从位置(x,y)出发,把棋盘的每一格都走一次且只走一次。找出所有路径。
(2) 问题分析
马是在棋盘的点上行走的,所以这里的棋盘是指行有N条边、列有M条边。而一个马在不出边界的情况下有8个方向
可以行走(走"日"字),如当前坐标为(x,y),则行走后的坐标可以为:
(x+1, y+2)
(x+1, y-2)
(x+2, y+1)
(x+2, y-1)
(x-1, y-2)
(x-1, y+2)
(x-2, y-1)
(x-2, y+1)
搜索的解空间是整个棋盘上的n*m个点。
约束条件是不出边界且每个点只经过一次。
搜索过程是从任一点(x,y)出发,按照深度优先的原则,从8个方向中尝试一个可以走的点,直到走过棋盘上所有
n*m个点。
5. Java代码实现
Java代码
package test;
/**
* create on 2010.05.21 TODO 回溯算法
*
* @author 毛正吉
* @version v1.0
*
*/
public class RecollectionSearch {
/**
* @param args
*/
public static void main(String[] args) {
// 注意(0<=x<n && 0<=y<m)
int n = 5;
int m = 4;
int x = 0;
int y = 0;
RecollectionSearch rs = new RecollectionSearch(n, m, x, y);
rs.find(x, y, 2);
System.out.println("######################");
System.out.println("总解数count=" + rs.getCount());
System.out.println("######################");
}
// 棋盘行数
private int n;
// 棋盘列数
private int m;
// 马的起始x坐标
private int x;
// 马的起始y坐标
private int y;
// 棋盘坐标
private int[][] a;
// 求解总数
private int count;
// "日"子x坐标
public int[] fx = { 1, 2, 2, 1, -1, -2, -2, -1 };
// "日"子y坐标
public int[] fy = { 2, 1, -1, -2, -2, -1, 1, 2 };
/**
* 构造方法
*
* @param _n
* @param _m
* @param _x
* @param _y
*/
public RecollectionSearch(int _n, int _m, int _x, int _y) {
n = _n;
m = _m;
x = _x;
y = _y;
a = new int
[m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
a[i][j] = 0;
}
}
// 马的起点
a[x][y] = 1;
count = 0;
}
public void find(int x, int y, int dep) {
int i, xx, yy;
for (i = 0; i < 7; i++) {
xx = x + fx[i];
yy = y + fy[i];
// 判断新坐标是否出界,是否已走过
if (check(xx, yy) == 1) {
a[xx][yy] = dep;
if (dep == n * m) {
output();
} else {
// 从新坐标出发,递归下一层
find(xx, yy, dep + 1);
}
// 回溯,恢复未走标志
a[xx][yy] = 0;
}
}
}
/**
* 判断新坐标是否出界,是否已走过
*
* @param xx
* @param yy
* @return
*/
public int check(int xx, int yy) {
if (xx >= n || yy >= m || xx < 0 || yy < 0 || a[xx][yy] != 0) {
return 0;
}
return 1;
}
/**
* 输出结果
*/
public void output() {
count++;
System.out.println("count=" + count);
for (int y = 0; y < n; y++) {
System.out.println("");
for (int x = 0; x < m; x++) {
System.out.print(a[y][x] + " ");
}
}
System.out.println("");
}
public int getN() {
return n;
}
public void setN(int n) {
this.n = n;
}
public int getM() {
return m;
}
public void setM(int m) {
this.m = m;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
回溯法是在包含问题的所有解得解空间树(或森林)中,按照深度优先的策略,从根结点出发搜索解空间树。
算法搜索至解空间树的任一结点时,总是先判断该结点是否满足问题的约束条件。如果满足进入该子树,继续
按照深度优先的策略进行搜索。否则,不去搜索以该结点为根的子树,而是逐层向其祖先结点回溯。
回溯法在用来求解问题的所有解时,要回溯到根,且根结点的所有可行的子树都已被搜索遍才结束。而回溯法
在用来求解问题的任一解时,只要搜索到问题的一个解就可以结束。适用于解决一些最优化问题。
2. 算法设计过程
(1) 确定问题的解空间
应用回溯法解决问题时,首先应明确定义问题的解空间。问题的解空间应至少包含问题的一个最优解。
(2) 确定结点的扩展规则
约束条件。
(3) 搜索解空间
回溯算法从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也
成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并
成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应该往回
移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中
搜索,直至找到所要求的解或解空间中已没有活结点时为止。
3. 算法框架
(1) 问题框架
设问题的解是一个n维向量(a1,a2,...,an),约束条件是ai(i1,2,...,n)之间满足某种条件,记为f(ai)。
(2) 非递归回溯框架
int a
, i;
i=1;
while(i>0(有路可走) and [未达到目标]){ //还未回溯到头
if(i>n){ //搜索到叶结点
搜索到一个解,输出;
}else{
a[i]第一个可能的值;
while(a[i]不满足约束条件且在搜索空间内)
a[i]下一可能的值;
if(a[i]在搜索空间内){
标识占用的资源;
i = i+1; //扩展下一个结点
}else{
清理所占的状态空间;
i = i-1; //回溯
}
}
}
(3)递归算法框架
int a
;
try(int i){
if(i>n){
输出结果;
}else{
for(j=下界; j<=上界; j++){//枚举i所有可能的路径
if(f(j)){ //满足限界函数和约束条件
a[i] = j;
... //其他操作
try(i+1);
a[i] = 0; //回溯前的清理工作(如a[i]置空)
}
}
}
}
4. 一个例子
(1) 问题描述
马的遍历问题。
在n*m的棋盘中,马只能走"日"字。马从位置(x,y)出发,把棋盘的每一格都走一次且只走一次。找出所有路径。
(2) 问题分析
马是在棋盘的点上行走的,所以这里的棋盘是指行有N条边、列有M条边。而一个马在不出边界的情况下有8个方向
可以行走(走"日"字),如当前坐标为(x,y),则行走后的坐标可以为:
(x+1, y+2)
(x+1, y-2)
(x+2, y+1)
(x+2, y-1)
(x-1, y-2)
(x-1, y+2)
(x-2, y-1)
(x-2, y+1)
搜索的解空间是整个棋盘上的n*m个点。
约束条件是不出边界且每个点只经过一次。
搜索过程是从任一点(x,y)出发,按照深度优先的原则,从8个方向中尝试一个可以走的点,直到走过棋盘上所有
n*m个点。
5. Java代码实现
Java代码
package test;
/**
* create on 2010.05.21 TODO 回溯算法
*
* @author 毛正吉
* @version v1.0
*
*/
public class RecollectionSearch {
/**
* @param args
*/
public static void main(String[] args) {
// 注意(0<=x<n && 0<=y<m)
int n = 5;
int m = 4;
int x = 0;
int y = 0;
RecollectionSearch rs = new RecollectionSearch(n, m, x, y);
rs.find(x, y, 2);
System.out.println("######################");
System.out.println("总解数count=" + rs.getCount());
System.out.println("######################");
}
// 棋盘行数
private int n;
// 棋盘列数
private int m;
// 马的起始x坐标
private int x;
// 马的起始y坐标
private int y;
// 棋盘坐标
private int[][] a;
// 求解总数
private int count;
// "日"子x坐标
public int[] fx = { 1, 2, 2, 1, -1, -2, -2, -1 };
// "日"子y坐标
public int[] fy = { 2, 1, -1, -2, -2, -1, 1, 2 };
/**
* 构造方法
*
* @param _n
* @param _m
* @param _x
* @param _y
*/
public RecollectionSearch(int _n, int _m, int _x, int _y) {
n = _n;
m = _m;
x = _x;
y = _y;
a = new int
[m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
a[i][j] = 0;
}
}
// 马的起点
a[x][y] = 1;
count = 0;
}
public void find(int x, int y, int dep) {
int i, xx, yy;
for (i = 0; i < 7; i++) {
xx = x + fx[i];
yy = y + fy[i];
// 判断新坐标是否出界,是否已走过
if (check(xx, yy) == 1) {
a[xx][yy] = dep;
if (dep == n * m) {
output();
} else {
// 从新坐标出发,递归下一层
find(xx, yy, dep + 1);
}
// 回溯,恢复未走标志
a[xx][yy] = 0;
}
}
}
/**
* 判断新坐标是否出界,是否已走过
*
* @param xx
* @param yy
* @return
*/
public int check(int xx, int yy) {
if (xx >= n || yy >= m || xx < 0 || yy < 0 || a[xx][yy] != 0) {
return 0;
}
return 1;
}
/**
* 输出结果
*/
public void output() {
count++;
System.out.println("count=" + count);
for (int y = 0; y < n; y++) {
System.out.println("");
for (int x = 0; x < m; x++) {
System.out.print(a[y][x] + " ");
}
}
System.out.println("");
}
public int getN() {
return n;
}
public void setN(int n) {
this.n = n;
}
public int getM() {
return m;
}
public void setM(int m) {
this.m = m;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
相关文章推荐
- Java基于循环递归回溯实现八皇后问题算法示例
- C#WPF实现回溯算法解决八皇后问题
- 【经典算法】:关于硬币称重问题及编程实现
- 马踏棋盘问题(骑士周游问题)及其优化算法java实现
- 关于最短路径图算法实现的问题
- 关于《射雕英雄传》的数学算法问题 JAVA实现
- c++实现回溯算法解决图的M着色问题
- 【Algorithm】关于n!被整除的问题【算法实现】
- 棋盘覆盖问题的算法实现
- 关于算法—— 一维字符串数组之间组合问题的C#实现
- 分治算法之 棋盘覆盖问题(完整代码实现)
- 棋盘覆盖问题算法分析与实现(递归)
- C++搜索与回溯算法之棋盘问题
- 棋盘覆盖问题的算法实现
- 关于《四元数插值算法实现游戏角色平滑旋转》中的算法一些问题
- 棋盘覆盖问题的算法实现
- 关于n!被整除的问题【算法实现】
- 棋盘覆盖问题的算法实现
- 关于RSA硬件算法的实现问题!
- 算法基础之python实现递归法中棋盘分割问题