马踏棋盘问题(骑士周游问题)及其优化算法java实现

本文介绍了一种使用深度优先搜索解决马踏棋盘问题的方法,并通过贪心算法进行了优化,以减少搜索的时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

马踏棋盘问题java实现(骑士周游问题)实际上是图的深度优先搜索(DFS)的应用或者进一步理解为一颗8叉树的深度遍历

package ccnu.offer.tree;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Scanner;

public class Demo07 {
	private static int X; // 棋盘的列数
	private static int Y; // 棋盘的行数
	private static boolean visited[]; // 用来标记棋盘上各个位置是否被访问过
	private static boolean finished; // 用来标记是否期盼所有位置均被访问(意味着已成功)

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.print("请输入棋盘的行数和列数:");
		do{
			Y = sc.nextInt();
			X = sc.nextInt();
		}while(X <= 0 || Y <= 0);
		System.out.print("请输入起始位置(先行数后列数):");
		int row, column;
		do{
			row = sc.nextInt();
			column = sc.nextInt();
		}while(row <= 0 || row > Y || column <= 0 || column > X);
		int[][] chessBoard = new int[Y][X];
		visited = new boolean[X * Y];
		long start = System.currentTimeMillis();
		traversalChessBoard(chessBoard, row - 1, column - 1, 1);
		long end = System.currentTimeMillis();
		System.out.println("共耗时:" + (end - start) + "ms");
		for(int[] rows : chessBoard){
			for(int columns : rows){
				System.out.print(columns + "\t");
			}
			System.out.println();
		} 
		sc.close();
	}

	public static void traversalChessBoard(int[][] chessBoard, int row, int column, int step) {
		chessBoard[row][column] = step;
		visited[row * X + column] = true; // 此位置已访问
		ArrayList<Point> ps = next(new Point(column, row)); // 由当前位置得到下一次所有位置的集合
		while (!ps.isEmpty()) {
			Point p = ps.remove(0);
			if (!visited[p.y * X + p.x]) { // 这个位置没有访问,那么就从这个位置开始进行下一次访问
				traversalChessBoard(chessBoard, p.y, p.x, step + 1);
			}
		}
		if(step < X * Y && !finished){ // (step < X * Y)这个条件成立,有两种情况:第一种,棋盘到目前为止仍没有走完;第二种,棋盘已经走完过,此时在回溯的过程中
			chessBoard[row][column] = 0; // 如果整个棋盘最终全部为零,则表示无解
			visited[row * X + column] = false;
		}else{
			finished = true;
		}
	}
	
	// 在当前位置p处,下一次的位置(最多有8个位置)
	public static ArrayList<Point> next(Point p) {
		ArrayList<Point> ps = new ArrayList<Point>();
		Point p1 = new Point(p);
		if ((p1.x = p.x - 2) >= 0 && (p1.y = p.y - 1) >= 0) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x - 1) >= 0 && (p1.y = p.y - 2) >= 0) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x + 1) < X && (p1.y = p.y - 2) >= 0) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x + 2) < X && (p1.y = p.y - 1) >= 0) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x + 2) < X && (p1.y = p.y + 1) < Y) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x + 1) < X && (p1.y = p.y + 2) < Y) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x - 1) >= 0 && (p1.y = p.y + 2) < Y) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x - 2) >= 0 && (p1.y = p.y + 1) < Y) {
			ps.add(new Point(p1));
		}
		return ps;
	}
}


贪心算法优化处理

    贪心算法(又称贪婪算法),是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。它只考虑局部的最优,从而让总体也最优。就我们这个马踏棋盘来说,我们每走一步都取最优的那个选择(而不是随便选择下一步),从而让整体的算法也最优。

   那么此问题的局部最优选择是什么呢?答案是,对于当前的一步,如果它的下一步有多种选择,我们应该选择它的下一步中的下一步选择最少的那个作为它的下一步。理由:因为只有在这样的选择方案下,当方案不符时,能够最快的回溯到当前的这一步上来(例如,它的下一步有4种选择,并且这4种选择的各自的下一步又分别有3,10,1,8种选择,而现在只有选择了下一步中的某一个才能成功,那么问题就是如何以最快的速度遍历出它的下一步中正确选择,也就是说当当前选择不对时,如何快速回溯到当前这一步,很显然,那只有它的下一步的下一步选择越少才会越快的结束这个选择),接着选择当前这一步的下一步选择次少作为它的下一步,依此类推。

package ccnu.offer.tree;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Scanner;

public class Demo07 {
	private static int X; // 棋盘的列数
	private static int Y; // 棋盘的行数
	private static boolean visited[]; // 用来标记棋盘上各个位置是否被访问过
	private static boolean finished; // 用来标记是否期盼所有位置均被访问(意味着已成功)
	private static int count = 1;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		do{
			System.out.print("请输入棋盘的行数和列数:");
			Y = sc.nextInt();
			X = sc.nextInt();
		}while(X <= 0 || Y <= 0);
		int row, column;
		do{
			System.out.print("请输入起始位置(先行数后列数):");
			row = sc.nextInt();
			column = sc.nextInt();
		}while(row <= 0 || row > Y || column <= 0 || column > X);
		int[][] chessBoard = new int[Y][X];
		visited = new boolean[X * Y];
		long start = System.currentTimeMillis();
		traversalChessBoard(chessBoard, row - 1, column - 1, 1);
		long end = System.currentTimeMillis();
		System.out.println("共耗时:" + (end - start) + "ms");
		for(int[] rows : chessBoard){
			for(int columns : rows){
				System.out.print(columns + "\t");
			}
			System.out.println();
		}
		sc.close();
	}

	public static void traversalChessBoard(int[][] chessBoard, int row, int column, int step) {
		chessBoard[row][column] = step;
		visited[row * X + column] = true; // 此位置已访问
		ArrayList<Point> ps = next(new Point(column, row)); // 由当前位置得到下一次所有位置的集合
		sort(ps); // 按照当前(new Point(column, row))这步的下一步的下一步选择数目进行非递减排序
		while (!ps.isEmpty()) {
			Point p = ps.remove(0); // 每次选择仍然未选中的下一步的下一步选择数目最少的下一步作为当前这步的下一步
			if (!visited[p.y * X + p.x]) { // 这个位置没有访问,那么就从这个位置开始进行下一次访问
				traversalChessBoard(chessBoard, p.y, p.x, step + 1);
			}
		}
		if(step < X * Y && !finished){ // (step < X * Y)这个条件成立,有两种情况:第一种,棋盘到目前为止仍没有走完;第二种,棋盘已经走完过,此时在回溯的过程中
			chessBoard[row][column] = 0; // 如果整个棋盘最终全部为零,则表示无解
			visited[row * X + column] = false;
		}else{ // 此处代表已成功走出覆盖棋盘的完整路径,如果此时将结果输出(去掉finished变量),以便回溯可以得到所有的结果
			finished = true;
		}
	}
	
	// 在当前位置p处,下一次的位置(最多有8个位置)
	public static ArrayList<Point> next(Point p) {
		ArrayList<Point> ps = new ArrayList<Point>();
		Point p1 = new Point(p);
		if ((p1.x = p.x - 2) >= 0 && (p1.y = p.y - 1) >= 0) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x - 1) >= 0 && (p1.y = p.y - 2) >= 0) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x + 1) < X && (p1.y = p.y - 2) >= 0) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x + 2) < X && (p1.y = p.y - 1) >= 0) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x + 2) < X && (p1.y = p.y + 1) < Y) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x + 1) < X && (p1.y = p.y + 2) < Y) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x - 1) >= 0 && (p1.y = p.y + 2) < Y) {
			ps.add(new Point(p1));
		}
		if ((p1.x = p.x - 2) >= 0 && (p1.y = p.y + 1) < Y) {
			ps.add(new Point(p1));
		}
		return ps;
	}
	
	// 根据当前的这一步的所有下一步的选择数目进行非递减排序
	public static void sort(ArrayList<Point> ps){
		ps.sort(new Comparator<Point>() {
			@Override
			public int compare(Point o1, Point o2) {
				int count1 = next(o1).size(); // 当前这一步o1的下一步的选择数目
				int count2 = next(o2).size();
				if (count1 < count2) {
					return -1;
				} else if (count1 == count2) {
					return 0;
				} else {
					return 1;
				}
			}
		});
	}
	
}





### 关于n×n棋盘算法与数据结构实现 #### 骑士周游问题棋盘骑士周游问题是经典的回溯算法应用之一,目标是在给定大小的棋盘上找到一条路径,使得骑士能够访问每一个格子恰好一次。为了提高效率,可以采用贪心策略优化搜索过程。 通过引入“下一步候选位置的数量最小化”的启发式规则,可以在每一步优先选择那些后续移动可能性较少的位置[^1]。这种做法有助于减少不必要的递归调用次数,从而提升整体性能。 以下是基于上述原理的一个Python版本实现: ```python def knight_tour(n, start_x=0, start_y=0): # 定义八个可能的方向向量 dx = [-2,-1, 1, 2, 2, 1,-1,-2] dy = [ 1, 2, 2, 1,-1,-2,-2,-1] board = [[-1]*n for _ in range(n)] # 初始化棋盘状态(-1表示未被访问过) next_moves_count = [[8]*n for _ in range(n)] def count_next(x,y): # 计算某个坐标处剩余合法步数 cnt = 0 for i in range(8): nx,ny=x+dx[i],y+dy[i] if 0<=nx<n and 0<=ny<n and board[nx][ny]==-1: cnt +=1 return cnt step_num =0 # 当前已走过几步 stack=[(start_x,start_y)] while len(stack)>0 : current_pos =stack[-1] if board[current_pos[0]][current_pos[1]]==-1: # 如果该节点尚未标记,则处理它 board[current_pos[0]][current_pos[1]]=step_num step_num+=1 min_k=-1 # 寻找具有最少未来选项的那个方向k temp_min=float('inf') for k in range(8): new_x,new_y=current_pos[0]+dx[k],current_pos[1]+dy[k] if not (0<=new_x<n and 0<=new_y<n ):continue # 越界跳过 if board[new_x][new_y]!=-1 : continue # 已经访问过的也跳过 num_of_future_options=count_next(new_x,new_y) # 统计这个新坐标的可用邻居数量 if num_of_future_options<temp_min: # 更新最优解条件下的索引以及对应的值 temp_min=num_of_future_options min_k=k if min_k!=-1: # 存在一个合理的选择就压栈继续探索下去 next_position=(current_pos[0]+dx[min_k],current_pos[1]+dy[min_k]) stack.append(next_position) elif len(stack)==n*n: # 所有方块都被成功遍历完成退出循环 break else: # 否则弹出当前顶点重新考虑其他分支情况 last_visited_node=stack.pop() board[last_visited_node[0]][last_visited_node[1]]=-1 step_num-=1 return board if __name__ == "__main__": result = knight_tour(5) for row in result: print(row) ``` 此代码片段展示了如何利用堆栈模拟深度优先搜索的过程来解决骑士巡游问题,并且融入了Waringham提出的Warnsdorff法则作为剪枝手段的一部分。 #### 棋盘路径计数问题 另一个常见的涉及二维网格的问题是从左上方走到右下方的不同路线数目统计。假设只允许向下或者往右两个动作,在不考虑障碍物的情况下可以直接运用动态规划的思想求得最终答案。 下面给出了一段Java语言编写的解决方案示例[^2]: ```java import java.util.*; public class Main{ public static long pathCount(int n,int m){ if(n==1||m==1)return 1; else { return(pathCount(n-1,m)+pathCount(n,m-1)); } } public static void main(String args[]){ Scanner sc=new Scanner(System.in); int N=sc.nextInt(); int M=sc.nextInt(); System.out.print(pathCount(N,M)); } } ``` 尽管这段程序逻辑简单明了,但由于采用了纯递归的方式计算结果,因此当输入规模较大时可能会遇到严重的性能瓶颈甚至导致栈溢出错误发生。对此可以通过记忆化技术加以改进,即预先创建一个数组用于存储中间运算成果以便重复利用,进而有效降低时间复杂度到O(n*m)[^2]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值