最近,微信小游戏上有一款叫做一笔画完的小游戏挺好玩,但是很多关卡要卡好几天,于是我写了一段Java程序来寻找一条一笔能画完的可行路径,有像我一样懒的小伙伴可以拿去通关,不谢~
解决思路:
一笔画完:要求在给定的网格中,从给定的起始点开始,依次不重复地经过所有格子,并且每个格子只能通往其四周(上下左右)的格子。思考的过程中,我将每一个格子看成一个点,并且将其与四周可达的点(格子看成点)相连。
之所以这样抽象,是为了使用图论中的相关知识来建模,这样的话网格就抽象成了一个图,那么一笔画完问题也就转化为了:由指定的起点,途中经过所有其他节点且只经过一次。这就是著名的哈密顿路径。
然后,从起点开始,选择一个默认方向的点走出,每走过一个点就将这个点和它相连的所有边从图中删除,新的点又当成起点继续寻路;那么,如果这样走下去发现是死路怎么办呢?没错,哪里来的就回哪里去,回到上一个走过的顶点,不过这里我们称之为回溯。每个点会记录其之后走过的点,回溯到上一个点后,去记录里查找还有没有没走过的点,有那就选择一个,继续往下走,没有则再回溯,..,若回溯到起点说明不存在这样的路径,图中的点已走完说明寻找到一条路径。
代码如下:(运行失败可调整虚拟机栈内存大小)
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
/**
* vx小游戏:一笔画完 寻路
* @author yinzw
*
*/
public class HmdPath {
private int m,n;//m行n列
private int[][] edge;//邻接矩阵
private int e = 0;//边数
private Stack<Integer> path = new Stack<>();//记录当前路径
private Stack<Set<Integer>> visited = new Stack<>();//记录当前路径中每个节点已经访问过的节点
private int[] dels;//删除的点
private int nodes;//图的顶点数
public HmdPath(int m, int n, int ... dels) {
super();
this.m = m;
this.n = n;
this.edge = new int[m*n][m*n];
this.dels = dels;
this.initEdges(dels);
}
//开始寻路
private boolean findPath() {
if(path.isEmpty()) {
System.out.println("请使用setStart(int)方法设置起始点");
return false;
}else
return findPath(path.peek());
}
//寻路,x已经在path中
private boolean findPath(int x) {
//死路判断
if(isToDeath(x)) {
backTrack(x);
//System.out.println("回溯");
return findPath(path.peek());
}
//优先选择度为1的节点,存在度为1的结点要么已经成功,要么这是一条死路
for(int j = 0; j < m * n; j++) {
if(j != x && !visited.peek().contains(j) && edge[x][j] == 1 && getDegree(j) == 1) {
if(path.size() == nodes - 1) {
//将该节点加入路径中
path.push(j);
//删除前一个节点所有边
delEdge(x);
//更新上一个节点的访问记录
visited.peek().add(j);
//初始化j节点的访问记录
Set<Integer> vis = new HashSet<>();
visited.push(vis);
return true;//成功!
}else {//这是一条死路
//回溯
backTrack(x);
return findPath(path.peek());//
}
}
}
//其次选择度为2的节点
for(int j = 0; j < m * n; j++) {
if(j != x && !visited.peek().contains(j) && edge[x][j] == 1 && getDegree(j) == 2) {
//将该节点加入路径中
path.push(j);
//删除前一个节点所有边
delEdge(x);
//更新上一个节点的访问记录
visited.peek().add(j);
//初始化j节点的访问记录
Set<Integer> vis = new HashSet<>();
visited.push(vis);
return findPath(j);
}
}
//任意找一个没有访问过的节点
for(int i = 0; i < m*n; i++) {
if(i != x && !visited.peek().contains(i) && edge[x][i] == 1) {
//将该节点加入路径中
path.push(i);
//删除x结点所有边
delEdge(x);
//更新x结点的访问记录
visited.peek().add(i);
//初始化i结点的访问记录
Set<Integer> vis = new HashSet<>();
visited.push(vis);
return findPath(i);
}
}
//没有路
if(path.size() == nodes) {
return true;
}
else if(path.size() < nodes) {
backTrack(x);
//System.out.println("回溯");
return findPath(path.peek());
}else {
System.out.println("很奇怪,没有路了");
return false;
}
}
//回溯到上一个结点
private boolean backTrack(int x) {
path.pop();//x出栈
visited.pop();//删除对应的访问记录
if(path.size() == 0) {
System.out.println("已回溯到根结点!");
return false;
}
initEdges(dels);//重新初始化图
//path栈中除栈顶节点,删除其他所有结点连接的边,完成图的回溯
for(int i = 0; i < path.size() - 1; i++) {
delEdge(path.get(i));
}
return true;
}
//获取节点的度
private int getDegree(int x) {
int degree = 0;
for(int j = 0; j < m * n; j++) {
//System.out.println("edge[" + x + "][" + j + "] =" + edge[x][j]);
if(edge[x][j] == 1 && j != x) {
++degree;
}
}
return degree;
}
private void initEdges() {
nodes = m*n;
e = 0;
for(int i = 0; i < m*n; i++) {
for(int j = 0; j < m*n; j++) {
if(Math.abs(i - j) == 1 && Math.max(i, j)%n != 0 || Math.abs(i - j) == n) {
edge[i][j] = 1;
//System.out.println(i + "," + j + " ");
if(i > j) {
e++;
}
}else
edge[i][j] = 0;
}
}
}
//设置起始点
private void setStart(int s) {
path.push(s);
Set<Integer> vis = new HashSet<Integer>();
visited.push(vis);
//findPath(s);
}
//初始化邻接矩阵
private void initEdges(int ... args) {
dels = args;
initEdges();
if(args != null) {
for(int i = 0; i < args.length; i++) {
delEdge(args[i]);
}
}
nodes = nodes - args.length;
}
//删除结点x相连的所有边
private void delEdge(int x) {
for(int j = 0; j< m * n; j++) {
if(edge[x][j] == 1) {
edge[x][j] = 0;
edge[j][x] = 0;
e--;
}
}
}
//判断从该结点出发是否为死路(可扩展)
private boolean isToDeath(int x) {
int deg2 = 0;
for(int j = 0; j < m * n; j++) {
if(j != x
&& !visited.peek().contains(j)
&& edge[x][j] == 1
&& getDegree(j) == 2) {
deg2++;
}
}
if(deg2 > 1) {
return true;
}else {
return false;
}
}
//打印图的邻接矩阵
private void printEdges() {
for(int i = 0; i < m*n; i++) {
for(int j = 0; j< m * n; j++) {
System.out.print(edge[i][j] + " ");
}
System.out.println();
}
System.out.println();
System.out.println("当前边数为:" + e);
}
private void printPath() {
System.out.println();
System.out.println("编号如下:");
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
int num = i * n + j;
if(num < 10) {
System.out.print(" " + num+ " ");
}else
System.out.print(num + " ");
}
System.out.println();
}
System.out.println();
System.out.println("一笔画:");
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
int num = i * n + j;
for(int t = 0; t < path.size();t++) {
if(num == path.get(t)) {
if(t < 10) {
System.out.print(" " + t+ " ");
}else
System.out.print(t + " ");
break;
}else if(t < path.size() - 1) {
continue;
}
System.out.print(" ");
}
}
System.out.println();
}
System.out.println();
}
public static void main(String[] args) {
HmdPath ybhw = new HmdPath(8,6,2,7,10,26,29); // 8和6表示8行6列的网格,2,7...29表示被挖去的格子
ybhw.setStart(37);//起点为37
ybhw.findPath();//寻路
System.out.println("path:length=" + ybhw.path.size());
ybhw.path.stream().forEach((x) -> System.out.print(x + "->") );
ybhw.printPath();//打印寻路结果
}
}
以上代码写的比较匆忙,未做任何优化,若有疑问或交流可发邮件至个人邮箱2583661140@qq.com