java - BFS算法在尝试解决15个难题时没有结束JAVA
问题描述
我正在尝试使用 Java 中的 BFS 算法为 15 个谜题创建求解器。问题是当我启动程序时,它以无限循环结束。我尝试过使用简单的输入状态,例如从已解决的状态中只交换 2 个数字并且它有效。它似乎很难解决更复杂的问题。
我检查了所有内容,队列是否填充了正确的状态,零索引是否符合状态。看起来它应该可以正常工作,但事实并非如此。这是我的代码:
程序.java
package Puzzle;
import java.awt.*;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Program {
public static final int[][] solved = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 0 } };
public static final State solvedState = new State(solved, 4, 4, new Point(3, 3));
static Solution solution;
public static void main(String[] args) throws IOException {
if(args.length != 5)
{
System.out.println("Wrong number of arguments in configuration. Given args:");
int nr = 0;
for (var arg : args)
{
System.out.println(nr++ + ": " + arg);
}
return;
}
State initialState = ReadInitialStateFromFile(args[2]);
switch (args[0])
{
case "bfs":
solution = new BFS(initialState, args[1]);
break;
case "dfs":
//solution = new DFS(initialState, args[1]);
break;
case "astr":
//solution = new AStar(initialState, args[1]);
break;
default:
System.out.println("incorrect method parameter");
return;
}
long starttime = System.nanoTime();
String solutionString = solution.FindSolution();
long endtime = System.nanoTime();
long elapsedTime = endtime - starttime;
long elapsedTimeMS = elapsedTime/1000000;
int solutionLength = solutionString != "No solution found!" ? solutionString.length() : -1;
WriteResultState(args[3], solutionString);
WriteAdditionalInfo(args[4],
solutionLength,
solution.numberOfVisitedStates,
solution.numberOfProcessedStates,
solution.maxDepth,
elapsedTimeMS
);
}
public static State ReadInitialStateFromFile(String filename) throws IOException {
String data = null;
int height = -1;
int width = -1;
Point point = new Point();
int [][] puzzle = null;
BufferedReader br = new BufferedReader(new FileReader(filename));
try {
data = br.readLine();
if(data == null || data.length() != 3)
{
throw new Exception("Dimentions are not correct");
}
String[] dimentions = data.split(" ");
height = Integer.parseInt(dimentions[0]);
width = Integer.parseInt(dimentions[1]);
puzzle = new int[width][height];
for(int i=0; i<width;i++){
String values[] = br.readLine().split(" ");
if(values.length != width)
{
throw new Exception(String.format("Values in row {0} are not correct", i));
}
for (int j = 0; j < height; j++)
{
int value = Integer.parseInt(values[j]);
//System.out.println(value);
if(value == 0)
{
point = new Point(i, j);
System.out.println("zero point" + point.toString());
}
puzzle[i][j] = value;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
br.close();
}
return new State(puzzle, height, width, point);
}
private static void WriteResultState(String resultStatePath, String solution) throws IOException {
int resultLenght = solution != "No solution found!" ? solution.length() : -1;
FileWriter fw = new FileWriter(resultStatePath);
fw.write(resultLenght);
if(resultLenght != -1)
fw.write(solution.toUpperCase());
fw.close();
}
private static void WriteAdditionalInfo(String additionalInfoPath, int resultLength, int numberOfVisitedStates, int numberOfProcessedStates, int maxDepth, long calculationTime) throws IOException {
FileWriter fw = new FileWriter(additionalInfoPath);
fw.write(resultLength);
fw.write(numberOfProcessedStates);
fw.write(numberOfVisitedStates);
fw.write(maxDepth);
fw.write(String.valueOf(calculationTime));
}
}
状态.java
package Puzzle;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class State {
public State previousState;
public List<State> nextStates;
public int[][] puzzle;
public char move;
public String moveSet;
public static int width, height;
public Point zeroIndex;
public int depth = 0;
public State(int[][] p, int _height, int _width, Point _zeroIndex) {
nextStates = new ArrayList<State>();
puzzle = new int[_height][_width];
width = _width;
height = _height;
puzzle = Arrays.copyOf(p, p.length);
zeroIndex = _zeroIndex;
moveSet = "";
}
public State(State state)
{
moveSet = state.moveSet;
nextStates = new ArrayList<State>();
puzzle = new int[state.height][state.width];
previousState = state.previousState;
for(int i=0; i < state.height;i++){
for(int j =0; j<state.width; j++)
{
this.puzzle[i][j] = state.puzzle[i][j];
}
}
//this.puzzle = Arrays.copyOf(state.puzzle, state.puzzle.length);
zeroIndex = new Point(state.zeroIndex);
}
public State Move(char direction)
{
State newState = new State(this);
newState.move = direction;
newState.moveSet += direction;
newState.previousState = this;
newState.depth = depth + 1;
switch (direction)
{
case 'u':
int tempu = newState.puzzle[zeroIndex.x][zeroIndex.y];
newState.puzzle[zeroIndex.x][zeroIndex.y] = newState.puzzle[zeroIndex.x - 1][zeroIndex.y];
newState.puzzle[zeroIndex.x - 1][zeroIndex.y] = tempu;
newState.zeroIndex.x--;
break;
case 'd':
int tempd = newState.puzzle[zeroIndex.x][zeroIndex.y];
newState.puzzle[zeroIndex.x][zeroIndex.y] = newState.puzzle[zeroIndex.x + 1][zeroIndex.y];
newState.puzzle[zeroIndex.x + 1][zeroIndex.y] = tempd;
newState.zeroIndex.x++;
break;
case 'l':
int templ = newState.puzzle[zeroIndex.x][zeroIndex.y];
newState.puzzle[zeroIndex.x][zeroIndex.y] = newState.puzzle[zeroIndex.x][zeroIndex.y-1];
newState.puzzle[zeroIndex.x][zeroIndex.y-1] = templ;
newState.zeroIndex.y--;
break;
case 'r':
int tempr = newState.puzzle[zeroIndex.x][zeroIndex.y];
newState.puzzle[zeroIndex.x][zeroIndex.y] = newState.puzzle[zeroIndex.x][zeroIndex.y + 1];
newState.puzzle[zeroIndex.x][zeroIndex.y + 1] = tempr;
newState.zeroIndex.y++;
// System.out.println(newState.zeroIndex.toString());
// System.out.println(this.zeroIndex.toString());
break;
default:
break;
}
return newState;
}
public void GenerateNextStates(String order)
{
char [] chars = order.toCharArray();
for(char direction : chars)
{
if(IsMovePossible(direction) == true && IsNotGoingBack(direction) == true)
{
this.nextStates.add(this.Move(direction));
}
}
}
public boolean IsMovePossible(char direction)
{
if ((!"udlr".contains(Character.toString(direction))) ||
(zeroIndex.x == 0 && direction == 'u') ||
(zeroIndex.x == height - 1 && direction == 'd') ||
(zeroIndex.y == 0 && direction == 'l') ||
(zeroIndex.y == width - 1 && direction == 'r'))
{
return false;
}
return true;
}
public void Print()
{ for(int i = 0; i < height;i++)
{
for (int j = 0; j < width; j++)
{
System.out.println(puzzle[i][j] + " ");
}
System.out.println();
}
}
public boolean IsSolution()
{
int correctValue = 1;
for (int i = 0; i < State.height; i++)
{
for (int j = 0; j < State.width; j++)
{
if (i == State.height - 1 && j == State.width - 1)
{
break;
}
if (puzzle[i][j] != correctValue++)
{
return false;
}
}
}
return true;
}
public boolean IsPuzzleSame(State other)
{
for (int i = 0; i < State.height; i++)
{
for (int j = 0; j < State.width; j++)
{
if(this.puzzle[i][j] != other.puzzle[i][j])
{
return false;
}
}
}
return true;
}
private boolean IsNotGoingBack(char direction)
{
if((this.move == 'l' && direction == 'r') ||
(this.move == 'u' && direction == 'd') ||
(this.move == 'r' && direction == 'l') ||
(this.move == 'd' && direction == 'u'))
{
//System.out.println("znaleziono powrót");
return false;
}
return true;
}
}
解决方案.java
package Puzzle;
import java.util.Queue;
public abstract class Solution {
public static State solutionState;
public static String neighborhoodSearchOrder;
public static int numberOfVisitedStates;
public static int numberOfProcessedStates;
public static int maxDepth;
public abstract String FindSolution();
protected boolean IsPuzzleStateNew(Queue<State> q, State newState)
{
for (State state : q)
{
if (state.IsPuzzleSame(newState))
{
return false;
}
}
return true;
}
}
BFS.java
package Puzzle;
import java.util.*;
public class BFS extends Solution {
public BFS(State _initialState, String _neighborhoodSearchOrder)
{
this.solutionState = _initialState;
this.neighborhoodSearchOrder = _neighborhoodSearchOrder.toLowerCase();
}
@Override
public String FindSolution() {
Queue<State> toVisit = new LinkedList<State>();
Queue<State> visited = new LinkedList<State>();
String solutionString = "";
boolean solutionReady = false;
toVisit.add(solutionState);
int z = 0;
while(toVisit.size() > 0)
{
// System.out.println("visited");
// for(int i=0; i<visited.size();i++){
// System.out.println("visited size: " + visited.size());
// }
//
//System.out.println(toVisit.size());
State currentState = toVisit.remove();
visited.add(currentState);
System.out.println("Numer iteracji: " + z);
//currentState.Print();
if(currentState.depth > maxDepth)
{
maxDepth = currentState.depth;
}
if(currentState.IsSolution())
{
solutionString = currentState.moveSet;
solutionReady = true;
break;
}
currentState.GenerateNextStates(neighborhoodSearchOrder);
Queue<State> allStates = new LinkedList<State>();
allStates.addAll(toVisit);
allStates.addAll(visited);
// for (State state: currentState.nextStates){
// System.out.println(state.zeroIndex.toString());
// state.Print();
// }
for (State state : currentState.nextStates)
{
if(IsPuzzleStateNew(allStates, state))
{
toVisit.add(state);
}
}
allStates.clear();
z++;
}
numberOfVisitedStates = visited.size() + toVisit.size();
numberOfProcessedStates = visited.size();
System.out.println(numberOfVisitedStates);
System.out.println(numberOfProcessedStates);
return solutionReady ? solutionString : "No solutionString found";
}
}
我花了很多时间来解决这个问题,我现在真的绝望了。提前感谢您的帮助。
解决方案
我在 4x4 拼图上测试了代码。它正确解决了最多需要 14 个步骤的难题,但需要很长时间。
我在 19 小时后停止了 15 步的解谜游戏。
为了加快代码速度,我hashCode()
在State
.
实施允许我通过使用for
和 made和冗余hashCode()
来更改昂贵的测试。
我还进行了重构,使其更通用、更快。if(IsPuzzleStateNew(allStates, state))
Set
visited
isPuzzleStateNew
isPuzzleSame
isSolution
时代发生了巨大的变化,最多 16 个步骤的谜题运行没有问题:
- 11 步拼图从 13 秒缩短到 0.2 秒
- 12 步拼图从 56 秒缩短到 0.2
- 13 步拼图从 100 秒降至 0.2
- 14 步拼图从 340 秒减少到 0.4
- 19 小时后停止 15 步拼图 0.6
- 16 步拼图需要 1.2
需要 35 和 80 步的测试拼图会崩溃,原因仍需调查。
在询问或回答时,您应该在mre中发布以下内容(您可以将 wntire 代码复制粘贴到Program.java
并运行):
import java.awt.Point;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
public class Program {
public static int[][] solve, solved;
public static State solvedState, initialState;
static Solution solution;
public static void main(String[] args) throws IOException {
solved = new int[][] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 0 } };
solvedState = new State(solved);
//run various tests (consider writing a JUnit test)
int[] testcases = {1, 4, 10, 11, 12, 13, 14, 15, 16, 35, 80};
for(int test : testcases) {
System.out.println("########## "+test+" ##########");
testCase(test);
solution = new BFS(initialState, solvedState, "udlr");
long starttime = System.nanoTime();
String solutionString = solution.findSolution();
long elapsedTimeMS = (System.nanoTime() - starttime)/1000000;
if(test != solutionString.length()) {
System.err.println("Test "+ test + " wrong length "+ solutionString.length() );
}
int solutionLength = solutionString != "No solution found!" ? solutionString.length() : -1;
WriteResultState(solutionString);
WriteAdditionalInfo(
solutionLength,
solution.getNumberOfVisitedStates(),
solution.getNumberOfProcessedStates(),
solution.getMaxDepth(),
elapsedTimeMS
);
}
}
private static void testCase(int numberOfTestCase){
switch (numberOfTestCase){
case 1:
solve = new int[][] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 0, 15 } };
break;
default: case 4: //4 steps to solve
solve = new int[][] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 0, 10, 11, 12 }, { 9, 13, 14, 15 } };
break;
case 10: // 10 steps to solve
solve = new int[][] { { 1, 3, 4, 8 }, { 6, 2, 7, 0 }, { 5, 10, 11, 12 }, { 9, 13, 14, 15 } };
break;
case 11:// 11 steps to solve
solve = new int[][] { { 1, 3, 4, 8 }, { 6, 2, 0, 7 }, { 5, 10, 11, 12 }, { 9, 13, 14, 15 } };
break;
case 12:// 12 steps to solve
solve = new int[][] { { 1, 3, 4, 8 }, { 6, 0, 2, 7 }, { 5, 10, 11, 12 }, { 9, 13, 14, 15 } };
break;
case 13:// 13 steps to solve
solve = new int[][] { { 1, 3, 4, 8 }, { 6, 10, 2, 7 }, { 5, 0, 11, 12 }, { 9, 13, 14, 15 } };
break;
case 14:// 14 steps to solve
solve = new int[][] { { 5, 1, 2, 4 }, { 6, 10, 3, 7 }, { 0, 14, 12, 8 }, { 9, 13, 11, 15 } };
//solve = new int[][] { { 1, 7, 2, 4},{ 9, 5, 3, 0},{ 6, 10, 12, 8},{ 13, 14, 11, 15} };
break;
case 15: // 15 steps to solve
solve = new int[][] { { 1, 7, 2, 0},{ 9, 5, 3, 4},{ 6, 10, 12, 8},{ 13, 14, 11, 15} };
break;
case 16:// 16 steps to solve
solve = new int[][] { { 0, 2, 11, 3 }, { 1, 6, 7, 4 }, { 5, 9, 12, 8 }, { 13, 10, 14, 15 } };
break;
case 35:// 35 steps to solve
solve = new int[][] { { 1, 10, 15, 4 }, { 13, 6, 3, 8 }, { 2, 9, 12, 7 }, { 14, 5, 0, 11 } };
break;
case 80:// 80 steps to solve. max possible steps for 15 puzzle
solve = new int[][] { { 0, 12, 9, 13 }, { 15, 11, 10, 14 }, { 3, 7, 2, 5 }, { 4, 8, 6, 1 } };
break;
}
initialState = new State(solve);
}
private static void WriteResultState(/*String resultStatePath,*/ String solution) throws IOException {
int resultLenght = solution != "No solution found!" ? solution.length() : -1;
System.out.println("solution length: "+resultLenght);
if(resultLenght != -1) {
System.out.println(solution.toUpperCase());
}
}
private static void WriteAdditionalInfo(/*String additionalInfoPath,*/int resultLength, int numberOfVisitedStates,
int numberOfProcessedStates, int maxDepth, long calculationTime) throws IOException {
System.out.println("Length: "+resultLength);
System.out.println("Processed states: "+numberOfProcessedStates);
System.out.println("Visited states : "+ numberOfVisitedStates);
System.out.println("Depth: "+maxDepth);
System.out.println("Time: "+String.valueOf(calculationTime));
}
}
class State {
//use private fields. access with getters / setters
private State previousState;
private final List<State> nextStates;
private final int[][] puzzle;
private char move;
private String moveSet;
private final int width, height; //should not be static
private final Point zeroIndex;
private int depth = 0;
public State(int[][] puzzle) {
nextStates = new ArrayList<>();
this.puzzle = puzzle;
width = puzzle[0].length;
height = puzzle.length;
zeroIndex = get0Location();
if(zeroIndex == null) throw new IllegalArgumentException("No 0 is puzzle");
moveSet = "";
}
public State(int[][] puzzle, int height, int width, Point zeroIndex) {
nextStates = new ArrayList<>();
this.width = width;
this.height = height;
this.puzzle = puzzle;
this.zeroIndex = zeroIndex;
moveSet = "";
}
public State(State state)
{
moveSet = state.moveSet;
nextStates = new ArrayList<>();
width = state.width;
height = state.height;
puzzle = new int[state.height][state.width];
previousState = state.previousState;
for(int i=0; i < state.height;i++){
puzzle[i] = Arrays.copyOf(state.puzzle[i], state.puzzle[i].length);
}
zeroIndex = new Point(state.zeroIndex);
}
public State move(char direction)
{
State newState = new State(this);
newState.move = direction;
newState.moveSet += direction;
newState.previousState = this;
newState.depth = depth + 1;
switch (direction)
{
case 'u':
int tempu = newState.puzzle[zeroIndex.x][zeroIndex.y];
newState.puzzle[zeroIndex.x][zeroIndex.y] = newState.puzzle[zeroIndex.x - 1][zeroIndex.y];
newState.puzzle[zeroIndex.x - 1][zeroIndex.y] = tempu;
newState.zeroIndex.x--;
break;
case 'd':
int tempd = newState.puzzle[zeroIndex.x][zeroIndex.y];
newState.puzzle[zeroIndex.x][zeroIndex.y] = newState.puzzle[zeroIndex.x + 1][zeroIndex.y];
newState.puzzle[zeroIndex.x + 1][zeroIndex.y] = tempd;
newState.zeroIndex.x++;
break;
case 'l':
int templ = newState.puzzle[zeroIndex.x][zeroIndex.y];
newState.puzzle[zeroIndex.x][zeroIndex.y] = newState.puzzle[zeroIndex.x][zeroIndex.y-1];
newState.puzzle[zeroIndex.x][zeroIndex.y-1] = templ;
newState.zeroIndex.y--;
break;
case 'r':
int tempr = newState.puzzle[zeroIndex.x][zeroIndex.y];
newState.puzzle[zeroIndex.x][zeroIndex.y] = newState.puzzle[zeroIndex.x][zeroIndex.y + 1];
newState.puzzle[zeroIndex.x][zeroIndex.y + 1] = tempr;
newState.zeroIndex.y++;
break;
}
return newState;
}
public void generateNextStates(String order)
{
char [] chars = order.toCharArray();
for(char direction : chars)
{
if(isMovePossible(direction) == true && isNotGoingBack(direction) == true)
{
nextStates.add(this.move(direction));
}
}
}
public boolean isMovePossible(char direction)
{
if (!"udlr".contains(Character.toString(direction)) ||
zeroIndex.x == 0 && direction == 'u' ||
zeroIndex.x == height - 1 && direction == 'd' ||
zeroIndex.y == 0 && direction == 'l' ||
zeroIndex.y == width - 1 && direction == 'r')
return false;
return true;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
for(int i = 0; i < height;i++)
{
for (int j = 0; j < width; j++)
{
sb.append(String.format("%1$4s", puzzle[i][j]));
}
sb.append("\n");
}
return sb.toString();
}
private boolean isNotGoingBack(char direction)
{
if(move == 'l' && direction == 'r' ||
move == 'u' && direction == 'd' ||
move == 'r' && direction == 'l' ||
move == 'd' && direction == 'u')
return false;
return true;
}
private Point get0Location() {
for (int row = 0; row < height; row++){
for (int col = 0; col < width ; col++){
if (puzzle[row][col] == 0) return new Point(row, col);
}
}
return null;
}
List<State> getNextStates() {
return nextStates;
}
String getMoveSet() {
return moveSet;
}
int getDepth() {
return depth;
}
int[][] getPuzzle(){
return puzzle;
}
@Override
public int hashCode() {
StringBuilder sb = new StringBuilder();
for(int[] row : puzzle) {
for(int i : row) {
sb.append(i);
}
}
return sb.toString().hashCode();
}
}
abstract class Solution {
protected State initialState, solutionState;
protected String neighborhoodSearchOrder;
protected int numberOfVisitedStates;
protected int numberOfProcessedStates;
protected int maxDepth;
public abstract String findSolution();
protected boolean isSolution(State state)
{
return Arrays.deepEquals(state.getPuzzle(), solutionState.getPuzzle());
}
public int getNumberOfVisitedStates() {
return numberOfVisitedStates;
}
public int getNumberOfProcessedStates() {
return numberOfProcessedStates;
}
public int getMaxDepth() {
return maxDepth;
}
}
class BFS extends Solution {
private State currentState;
private long starttime ;
public BFS(State initialState,State solutionState, String neighborhoodSearchOrder)
{
this.initialState = initialState;
this.solutionState = solutionState;
this.neighborhoodSearchOrder = neighborhoodSearchOrder.toLowerCase();
}
@Override
public String findSolution() {
starttime = System.nanoTime();
Queue<State> toVisit = new LinkedList<>();
Set<State> visited = new HashSet<>();
String solutionString = "";
boolean solutionReady = false;
numberOfVisitedStates = 0;
numberOfProcessedStates = 0;
maxDepth = 0;
toVisit.add(initialState);
visited.add(initialState);
while(toVisit.size() > 0)
{
currentState = toVisit.remove();
numberOfProcessedStates++;
if(currentState.getDepth() > maxDepth)
{
maxDepth = currentState.getDepth();
System.out.println("Processed: "+ numberOfProcessedStates +" nodes, depth: " + maxDepth+ " in "
+ (System.nanoTime() - starttime)/1000000000 + " seconds " );
}
if(isSolution(currentState))
{
solutionString = currentState.getMoveSet();
solutionReady = true;
break;
}
currentState.generateNextStates(neighborhoodSearchOrder);
for (State state : currentState.getNextStates())
{
if( visited.add(state))
{
toVisit.add(state);
}
}
}
numberOfVisitedStates = numberOfProcessedStates+ toVisit.size();
//System.out.println("End state: "+ "\n" + currentState);
return solutionReady ? solutionString : "No solutionString found";
}
}
为了进一步改进,我建议: -在尝试解决之前检查可解决
性。
- 检查是否visited
需要。
旁注:请参阅Java 命名约定。
推荐阅读
- javascript - 触发时如何使语义-ui侧边栏不使页面内容变暗?
- python - 我的神经网络没有学习(负 R_Squared,总是相同的损失,分类输入数据,回归)
- c - 尝试生成有序链表的意外输出
- openssl - openssl:验证签名但忽略到期日期
- graphql - 如何修复内容引用类型的 Gatsby 查询
- c# - 无法与自托管 WCF 服务通信
- reactjs - 在 BackButton 上:尽管 url 已更改,但组件不会更新
- docker-compose - 错误:yaml.parser.ParserError:解析块映射时
- fluentvalidation - 使用 RuleSet 进行 FluentValidation 测试
- c++ - 如何使用 dlmopen 在不同线程中打开多个共享库?