使用DFS解决8-Puzzle

时间:2014-08-10 15:49:09

标签: java artificial-intelligence depth-first-search 8-puzzle state-space

我正在寻找java中的代码,它通过给定的初始状态实现8-puzzle游戏的DFS和BFS:

1 2 3
8 0 4
7 6 5

和目标状态

2 8 1
0 4 3
7 6 5

我需要打印从初始状态到目标状态的解决方案路径(尚未完成)

这是我的代码。到目前为止,我只能实现DFS。到目前为止,我的程序执行的是一旦找到目标状态就输出SUCCESS。 然而,它永远不会达到这一点。

有人可以告诉我哪里出错了吗?

3 个答案:

答案 0 :(得分:6)

好的,所以你的程序花费的时间比预期的要长。首先,我们要确定它是否陷入无限循环,或者只是缓慢。为此,让程序通过在主循环中添加以下内容来打印其进度:

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

然后我们看到该程序每秒访问了几千个状态。由于我们的处理器每秒执行数十亿条指令,这意味着处理状态需要大约一百万条cpu指令。它不应该那么高,不是吗?那么可能导致这种情况呢?

一般来说,我们现在使用分析器来测量代码的所有部分花费的时间,但由于程序太短,我们可以先尝试猜测。我的第一个猜测是打印我们访问的每个州都可能非常昂贵。为了验证这一点,我们只打印每1000个状态:

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

我们改变了地方,我们注意到前5000个国家在第二个国家被访问,所以印刷确实很重要。我们还注意到一些奇怪的事情:虽然前一个5000状态在一秒钟内被访问,但由于某种原因,程序似乎变得越来越慢。在访问的20000个州,大约需要一秒钟才能访问1000个州,并且情况会持续恶化。这是意料之外的,因为处理状态不应该变得越来越昂贵。所以我们知道循环中的一些操作变得越来越昂贵。让我们回顾一下我们的代码,以确定它可能是什么操作。

无论集合的大小如何,推送和弹出都需要持续时间。但是你也使用Stack.search和LinkedList.contains。记录这两个操作都要求迭代整个堆栈或列表。所以,让我们输出这些集合的大小:

        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个元素,并且CLOSED接近40000个。这就解释了为什么处理状态变得越来越慢。因此,我们希望选择具有更高效的包含操作的数据结构,例如java.util.HashSetjava.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();
}

几乎立即打印“SUCCESS”。

答案 1 :(得分:2)

我建议您使用Hipster library轻松解决8-puzzle,使用BFS,DFS,A *,IDA *等。有一个full example here(它可以帮助您设计你的搜索策略)。

如果您感兴趣,解决问题的基本步骤是首先定义允许您遍历状态空间搜索问题的函数,然后选择一个算法来搜索状态空间问题。要创建搜索问题,您可以使用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

答案 2 :(得分:1)

您不应该推入已添加到其中的已打开堆栈组合。 (另外,一个ArrayDeque会更好,Stack是一个老类,看他javadoc http://docs.oracle.com/javase/7/docs/api/java/util/Stack.html

  

一套更完整,更一致的LIFO堆栈操作   由Deque接口及其实现提供,应该   优先使用此类。例如:

     

Deque stack = new ArrayDeque();   )

为避免无数次浏览相同的状态,您必须使用Set作为关闭列表,并验证您尝试在打开列表中添加的状态是否从未添加到关闭列表中。

此外,您可能更习惯使用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");
    }
}

这具有对图中任何类型的搜索都是通用的优点。如果你想解决另一个难题,你只需要修改我没有实现的方法(它实际上是Node接口的一部分)。如果您想更改算法,例如通常用于8-puzzle的A星,您只需更改solve方法即可。我希望这段代码可以帮到你。