Решение 8-головоломка с использованием DFS

Я ищу код на java, который реализует DFS и BFS для игры 8-puzzle по заданному исходному состоянию:

1 2 3
8 0 4
7 6 5

государство и цель

2 8 1
0 4 3
7 6 5

мне нужно распечатать путь решения от начального до целевого состояния (еще не сделано)

это код у меня есть. До сих пор я только смог реализовать DFS. До сих пор моя программа добивается успеха, как только находит состояние цели. Однако до этого не доходит.

кто-то может скажи мне, где я ошибаюсь?

3 ответов


Ok, поэтому ваша программа занимает больше времени, чем ожидалось. Сначала мы хотим узнать, застрял ли он в бесконечном цикле или просто медленно. Чтобы сделать это, пусть программа распечатает свой прогресс, добавив в основной цикл следующее:

    int statesVisited = 0;
    while (OPEN.empty() == false && STATE == false) {
        statesVisited++;
        System.out.println(statesVisited);

затем мы видим, что программа посетила пару тысяч государств в секунду. Поскольку наш процессор выполняет несколько миллиардов инструкций в секунду, это означает, что обработка состояния занимает около миллиона инструкций процессора. Этого не должно быть. что высокий, это следует? Так что же могло быть причиной этого?

вообще говоря, теперь мы будем использовать профилировщик, чтобы измерить, в какой части кода все это время тратится, но так как программа настолько коротка, мы можем попробовать угадать сначала. Мое первое предположение, что печать каждого штата, который мы посещаем, может быть довольно дорогой. Чтобы проверить это, давайте напечатаем только каждое 1000-е состояние:

    while (OPEN.empty() == false && STATE == false) {
        statesVisited++;
        if (statesVisited % 1000 == 0) {
            System.out.println(statesVisited);
        }

мы что изменение в месте, мы замечаем что первые 5000 государств посещены внутри под вторым, поэтому печать была действительно значительной. Мы также замечаем кое-что любопытное: в то время как первые 5000 состояний посещаются менее чем за секунду, по какой-то причине программа становится все медленнее и медленнее. При посещении 20000 Штатов требуется около секунды, чтобы посетить 1000 Штатов, и это продолжает ухудшаться. Это неожиданно, так как обработка состояния не должна становиться все более дорогостоящей. Таким образом, мы знаем, что некоторые операции в нашем цикле становятся все более дорогими. Давайте рассмотрим наш код, чтобы определить, какая операция это может быть.

Pushing и popping занимает постоянное время независимо от размера коллекции. Но вы также используете Stack.поиск и LinkedList.содержит. Обе эти операции документированы, чтобы потребовать итерации по всему стеку или списку. Итак, давайте выведем размеры этих коллекций:

        if (statesVisited % 1000 == 0) {
            System.out.println(statesVisited);
            System.out.println(OPEN.size());
            System.out.println(CLOSED.size());
            System.out.println();
        }

подождав немного, мы видим:

40000
25947
39999

таким образом, OPEN содержит 25000 элементов и закрыт почти 40000. Это объясняет, почему обработка состояния продолжает все медленнее и медленнее. Поэтому мы хотим выбрать структуры данных с более эффективной операцией contains, такой как java.util.HashSet или java.util.LinkedHashSet (который является гибридом между хэш-набором и связанным списком, позволяя нам извлекать элементы в порядке их добавления). Делая это, мы получаем:

public static LinkedHashSet<String> OPEN = new LinkedHashSet<String>();
public static HashSet<String> CLOSED = new HashSet<String>();
public static boolean STATE = false;

public static void main(String args[]) {

    int statesVisited = 0;

    String start = "123804765";
    String goal = "281043765";
    String X = "";
    String temp = "";

    OPEN.add(start);

    while (OPEN.isEmpty() == false && STATE == false) {

        X = OPEN.iterator().next();
        OPEN.remove(X);

        int pos = X.indexOf('0'); // get position of ZERO or EMPTY SPACE
        if (X.equals(goal)) {
            System.out.println("SUCCESS");
            STATE = true;
        } else {
            // generate children
            CLOSED.add(X);

            temp = up(X, pos);
            if (!(temp.equals("-1")))
                OPEN.add(temp);
            temp = left(X, pos);
            if (!(temp.equals("-1")))
                OPEN.add(temp);
            temp = down(X, pos);
            if (!(temp.equals("-1")))
                OPEN.add(temp);
            temp = right(X, pos);
            if (!(temp.equals("-1")))
                OPEN.add(temp);
        }
    }

}

/*
 * MOVEMENT UP
 */
public static String up(String s, int p) {
    String str = s;
    if (!(p < 3)) {
        char a = str.charAt(p - 3);
        String newS = str.substring(0, p) + a + str.substring(p + 1);
        str = newS.substring(0, (p - 3)) + '0' + newS.substring(p - 2);
    }
    // Eliminates child of X if its on OPEN or CLOSED
    if (!OPEN.contains(str) && CLOSED.contains(str) == false)
        return str;
    else
        return "-1";
}

/*
 * MOVEMENT DOWN
 */
public static String down(String s, int p) {
    String str = s;
    if (!(p > 5)) {
        char a = str.charAt(p + 3);
        String newS = str.substring(0, p) + a + str.substring(p + 1);
        str = newS.substring(0, (p + 3)) + '0' + newS.substring(p + 4);
    }

    // Eliminates child of X if its on OPEN or CLOSED
    if (!OPEN.contains(str) && CLOSED.contains(str) == false)
        return str;
    else
        return "-1";
}

/*
 * MOVEMENT LEFT
 */
public static String left(String s, int p) {
    String str = s;
    if (p != 0 && p != 3 && p != 7) {
        char a = str.charAt(p - 1);
        String newS = str.substring(0, p) + a + str.substring(p + 1);
        str = newS.substring(0, (p - 1)) + '0' + newS.substring(p);
    }
    // Eliminates child of X if its on OPEN or CLOSED
    if (!OPEN.contains(str) && CLOSED.contains(str) == false)
        return str;
    else
        return "-1";
}

/*
 * MOVEMENT RIGHT
 */
public static String right(String s, int p) {
    String str = s;
    if (p != 2 && p != 5 && p != 8) {
        char a = str.charAt(p + 1);
        String newS = str.substring(0, p) + a + str.substring(p + 1);
        str = newS.substring(0, (p + 1)) + '0' + newS.substring(p + 2);
    }
    // Eliminates child of X if its on OPEN or CLOSED
    if (!OPEN.contains(str) && CLOSED.contains(str) == false)
        return str;
    else
        return "-1";
}

public static void print(String s) {
    System.out.println(s.substring(0, 3));
    System.out.println(s.substring(3, 6));
    System.out.println(s.substring(6, 9));
    System.out.println();
}

, который печатает "успех" почти мгновенно.


Я бы предложил вам использовать хипстер библиотека легко решить 8-головоломку, используя BFS, DFS, A*, IDA* и т. д. Есть полный пример здесь (это может помочь вам разработать стратегию поиска).

Если вы заинтересованы, основными шагами для решения проблемы являются сначала определение функций, которые позволяют вам пройти проблему поиска в пространстве состояний, а затем выбрать один алгоритм для поиска по проблеме пространства состояний. Для создания поиска проблема, вы можете использовать ProblemBuilder класс:

SearchProblem p = 
  ProblemBuilder.create()
    .initialState(Arrays.asList(5,4,0,7,2,6,8,1,3))
    .defineProblemWithExplicitActions()
    .useActionFunction(new ActionFunction<Action, List<Integer>>() {
    @Override
    public Iterable<Action> actionsFor(List<Integer> state) {
        // Here we compute the valid movements for the state
        return validMovementsFor(state);
    }
    }).useTransitionFunction(new ActionStateTransitionFunction<Action, List<Integer>>() {
    @Override
    public List<Integer> apply(Action action, List<Integer> state) {
        // Here we compute the state that results from doing an action A to the current state
        return applyActionToState(action, state);
    }
    }).useCostFunction(new CostFunction<Action, List<Integer>, Double>() {
    @Override
    public Double evaluate(Transition<Action, List<Integer>> transition) {
        // Every movement has the same cost, 1
        return 1d;
    }
    }).build();

как только у вас есть определение проблемы, вы можете выбрать любой алгоритм для ее решения:

System.out.println(Hipster.createDijkstra(p).search(Arrays.asList(0,1,2,3,4,5,6,7,8)));

вы можете прочитать более подробную информацию о проблеме 8-puzzle и как ее решить с помощью Hipster в этой презентации https://speakerdeck.com/pablormier/hipster-an-open-source-java-library-for-heuristic-search


вы не должны нажимать комбинации открытого стека, которые уже были добавлены к нему. (Кроме того, ArrayDeque был бы лучше, стек-старый класс, см. Он javadoc http://docs.oracle.com/javase/7/docs/api/java/util/Stack.html

более полный и последовательный набор операций стека LIFO предоставлено интерфейсом Deque и его реализациями, которые должны предпочтительно использовать этот класс. Например:

стек Deque = новый ArrayDeque(); )

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

кроме того, вам может быть удобнее использовать массивы byte [] (не int[] для сохранения памяти), а не строки для выполнения ваших операций.

подводя итог, вы можете структурировать свой код следующим образом:

public class Taquin {
    private byte[][] state = new byte[3][3];

    public Taquin(String s) { ... }
    public List<Taquin> successors() { ... }
    public boolean isSolvable(Taquin goal) { ... }
    //Necessary to use the Set !////////////
    public int hashCode() { ... }
    public boolean equals(Object o) { ... }
    public String toString() { ...state }
    ////////////////////////////////////////

    public void solve(Taquin goal) { 
        if (isSolvable(goal)) {
            Deque<Taquin> open   = new ArrayDeque<>();
            Set<Taquin>   closed = new HashSet<>();
            closed.add(this);
            open.add(this);

            Taquin current = this;
            //if isSolvable is correct you should never encounter open.isEmpty() but for safety, test it
            while (!current.equals(goal) && !open.isEmpty()) {
                current = open.pop();
                System.out.println(current);
                for (Taquin succ : current.successors())
                    //we only add to the open list the elements which were never "seen"
                    if (closed.add(succ))
                        open.add(succ);
            }
            System.out.println("Success");
        } else
            System.out.println("No solution");
    }
}

Это преимущество быть общим для любого вида поиска в графике. Если вы хотите решить другую головоломку, вы просто измените метод, который я не реализовал (который на самом деле является частью интерфейса узла). Если вы хотите изменить алгоритм, например, звезду, которая часто используется для 8-головоломки, вы просто измените метод решения. Надеюсь, этот образец кода вам поможет.