Поиск в глубину завершается досрочно

Я создаю программу на Java, которая решает n-puzzle без использования эвристики, просто просто с поиском в глубину и в ширину пространства состояний. Я немного борюсь с моей реализацией поиска в глубину. Иногда он решит данную загадку, но иногда кажется, что он рано сдается.

Вот мой класс DFS. DepthFirstSearch() передается PuzzleBoard, который изначально создается путем перетасовки решенной доски (чтобы гарантировать, что доска находится в решаемом состоянии).

public class DepthFirst {
static HashSet<PuzzleBoard> usedStates = new HashSet<PuzzleBoard>(); 

public static void DepthFirstSearch(PuzzleBoard currentBoard)
{   
    // If the current state is the goal, stop.
    if (PuzzleSolver.isGoal(currentBoard)) {    
        System.out.println("Solved!");
        System.exit(0); 
    } 

    // If we haven't encountered the state before,
    // attempt to find a solution from that point.
    if (!usedStates.contains(currentBoard)) {                       
        usedStates.add(currentBoard);           
        PuzzleSolver.print(currentBoard);

        if (PuzzleSolver.blankCoordinates(currentBoard)[1] != 0) {
            System.out.println("Moving left");
            DepthFirstSearch(PuzzleSolver.moveLeft(currentBoard));
        }
        if (PuzzleSolver.blankCoordinates(currentBoard)[0] != PuzzleSolver.n-1) {
            System.out.println("Moving down");
            DepthFirstSearch(PuzzleSolver.moveDown(currentBoard)); 
        }
        if (PuzzleSolver.blankCoordinates(currentBoard)[1] != PuzzleSolver.n-1) {
            System.out.println("Moving right");
            DepthFirstSearch(PuzzleSolver.moveRight(currentBoard)); 
        }
        if (PuzzleSolver.blankCoordinates(currentBoard)[0] != 0) {
            System.out.println("Moving up");
            DepthFirstSearch(PuzzleSolver.moveUp(currentBoard));    
        }

        return; 
    } else {
        // Move up a level in the recursive calls
        return; 
    }
}   
}

Я могу утверждать, что мои методы и логика moveUp(), moveLeft(), moveRight() и moveDown() работают правильно, поэтому проблема должна быть где-то в другом месте.

Вот мой класс объектов PuzzleBoard с методами hashCode и equals:

static class PuzzleBoard {
    short[][] state;        
    /**
     * Default constructor for a board of size n
     * @param n Size of the board 
     */
    public PuzzleBoard(short n) {
        state = PuzzleSolver.getGoalState(n);
    }
    public PuzzleBoard(short n, short[][] initialState) {
        state = initialState; 
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Arrays.deepHashCode(state);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        PuzzleBoard other = (PuzzleBoard) obj;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (state[i][j] != other.state[i][j])
                    return false; 
            }
        }
        return true;
    }   
}

Как указывалось ранее, иногда поиск работает правильно и находит путь к решению, но в других случаях он останавливается до того, как находит решение и до того, как заканчивается память.

Вот фрагмент вывода, начинающийся за несколько ходов до того, как поиск прекращает поиск.

...
Moving down
6 1 3 
5 8 2 
0 7 4 
Moving right
6 1 3 
5 8 2 
7 0 4 
Moving left
Moving right
Moving up
6 1 3 
5 0 2 
7 8 4 
Moving left
Moving down
Moving right
Moving up
Moving up
Moving right
Moving down
Moving up
Moving down
Moving up
Moving down
Moving up
Moving down
Moving up
Moving down
...

Я урезал его раньше для краткости, но в итоге он просто перемещается вверх и вниз десятки раз и никогда не достигает решенного состояния.

Может ли кто-нибудь пролить свет на то, что я делаю неправильно?

Изменить: это MoveUp(). Аналогично реализованы остальные методы перемещения.

/**
 * Move the blank space up
 * @return The new state of the board after the move
 */
static PuzzleBoard moveUp(PuzzleBoard currentState) {
    short[][] newState = currentState.state; 
        short col = blankCoordinates(currentState)[0]; 
        short row = blankCoordinates(currentState)[1]; 
        short targetCol = col; 
        short targetRow = row; 
        newState[targetCol][targetRow] = currentState.state[col - 1][row];
        newState[targetCol - 1][targetRow] = 0; 

        return new PuzzleBoard(n, newState); 
    }

person Michelle    schedule 27.11.2013    source источник
comment
Вы уверены, что он генерирует разрешимую головоломку?   -  person Adam Arold    schedule 27.11.2013
comment
Способ, которым я создаю свою головоломку для решения, заключается в том, что я начинаю с решенного состояния, а затем делаю оттуда случайное количество допустимых ходов. Так что да, я начинаю с разрешимого состояния.   -  person Michelle    schedule 27.11.2013
comment
Просто предположение, так как я не совсем понимаю операторы if(), чтобы определить, в каком направлении двигаться; но я чувствую, что он застрял в углу. Поскольку вы используете if(), а не else if(), возможно, он пытается двигаться вверх, а затем сразу же возвращается вниз. Затем снова пытается подняться?   -  person DoubleDouble    schedule 27.11.2013
comment
Где код PuzzleSolver?   -  person MadConan    schedule 27.11.2013
comment
Последовательность перемещений Moving up, Moving down должна быть недействительной, так как она ведет к предыдущему состоянию. Вы должны проверить свой метод equals, если он действительно работает так, как ожидалось (а также ваш метод hashCode()). Напишите для них тесты...   -  person MrSmith42    schedule 27.11.2013
comment
@MrSmith42 нет Перемещение вверх и вниз является допустимым движением, поскольку вы не знаете, из какого предыдущего состояния появилось текущее состояние, и я думаю, что единственный способ - проверить его в хэш-наборе, который сделал OP.   -  person Vikram Bhat    schedule 27.11.2013
comment
@Michelle - это PuzzleSolver.moveLeft(Current) и другие подобные операторы, создающие новый объект доски или просто изменяющие старый? В идеале он должен возвращать новый объект, иначе DFS не будет работать правильно в этой реализации.   -  person Vikram Bhat    schedule 27.11.2013
comment
@VikramBhat она проверяет equals() для этого: if (this == obj) {return true;}. Кроме того, она упомянула, что иногда ее DFS находит решение правильно, поэтому я предполагаю, что проблема не в этом.   -  person ile    schedule 27.11.2013
comment
@VikramBhat Я добавил одну из функций move() из класса PuzzleSolver для большего контекста.   -  person Michelle    schedule 27.11.2013
comment
@Michelle Пожалуйста, попробуйте то, что я упомянул в своем ответе, так как остальная часть вашей логики кажется правильной, просто есть сомнения с хэш-кодом и равными. Вы не нуждаетесь в них, если делаете, как я предложил.   -  person Vikram Bhat    schedule 27.11.2013


Ответы (2)


В прошлом у меня было много проблем с хэш-набором. Лучше всего попробовать не хранить объект в хэш-наборе, а попытаться закодировать ваш объект в строку.

Вот как это сделать: -

StringBuffer encode(PuzzleBoard b) {

   StringBuffer buff = new StringBuffer();

    for(int i=0;i<b.n;i++) {

      for(int j=0;j<b.n;j++) {

          // "," is used as separator
            buff.append(","+b.state[i][j]);

      }
   }
   return buff;
}

Make two changes in the code:-

if(!usedStates.contains(encode(currentBoard))) {

usedStates.add(encode(currentBoard));
......

}

Примечание:- Здесь нет необходимости писать свою собственную функцию хеш-кода, а также нет необходимости реализовывать функцию equals, поскольку Java сделала это за вас в StringBuffer.

person Vikram Bhat    schedule 27.11.2013

У меня есть одна из проблем в вашей реализации: -

В следующем коде: -

static PuzzleBoard moveUp(PuzzleBoard currentState) {
    short[][] newState = currentState.state; 
        short col = blankCoordinates(currentState)[0]; 
        short row = blankCoordinates(currentState)[1]; 
        short targetCol = col; 
        short targetRow = row; 
        newState[targetCol][targetRow] = currentState.state[col - 1][row];
        newState[targetCol - 1][targetRow] = 0; 

        return new PuzzleBoard(n, newState); 
    }

Здесь вы используете ссылку на тот же массив, что и newState, из currentState.state, поэтому, когда вы вносите изменения в newState, ваш currentState.state также изменится, что повлияет на DFS при возврате вызова. Чтобы предотвратить это, вы должны инициализировать новый массив. Вот что нужно сделать: -

static PuzzleBoard moveUp(PuzzleBoard currentState) {
    short[][] newState = new short[n][n]; 
        short col = blankCoordinates(currentState)[0]; 
        short row = blankCoordinates(currentState)[1]; 
        short targetCol = col; 
        short targetRow = row; 
        for(int i=0;i<n;i++) {

          for(int j=0;j<n;j++) {

               newState[i][j] = currentState.state[i][j];
           }

        }
        newState[targetCol][targetRow] = currentState.state[col - 1][row];
        newState[targetCol - 1][targetRow] = 0; 

        return new PuzzleBoard(n, newState); 
    }

Сделайте это изменение для всех движений вверх, движение вниз....

Более того, я не думаю, что ваш хэш-набор работает правильно, потому что если бы это было так, вы всегда находили бы свое новое состояние в хэш-наборе, и ваша программа останавливалась бы. Как и в случае равенства, вы сравниваете массивы состояний с одной и той же ссылкой, поэтому всегда будет истинно. Пожалуйста, попробуйте использовать мою функцию кодирования как хэш.

person Vikram Bhat    schedule 28.11.2013