8

我正在寻找通过给定初始状态为 8 拼图游戏实现 DFS 和 BFS 的 java 代码:

1 2 3
8 0 4
7 6 5

和目标状态

2 8 1
0 4 3
7 6 5

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

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

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

4

3 回答 3

7

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

    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.HashSetor 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();
}

几乎立即打印“SUCCESS”。

于 2014-08-10T17:06:03.580 回答
2

我建议你使用Hipster 库来轻松解决 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)));

您可以在此演示文稿https://speakerdeck.com/pablormier/hipster-an-open-source-java-library-for-heuristic-search中阅读有关 8 拼图问题以及如何使用 Hipster 解决它的更多详细信息

于 2014-08-11T11:04:30.610 回答
1

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

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

双端队列栈 = 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 谜题的星形,您只需更改求解方法即可。我希望这个代码示例对您有所帮助。

于 2014-08-10T16:03:47.873 回答