滑动谜题通解--广度优先算法(BFS)

滑动谜题是一类经典的智力游戏,广泛应用于人工智能、算 法设计与分析的研究中。通过研究滑动拼图问题,可以深入理解搜索 算法、启发式算法以及状态空间的表示与优化。这不仅有助于提高解 决问题的能力,还能为实际应用中的路径规划、机器人导航等问题提 供理论支持。

广度优先算法(BFS)

广度优先搜索算法(又称宽度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。

广度优先算法的核心思想是:从初始节点开始,应用算符生成第一层节点,检查目标节点是否在这些后继节点中,若没有,再用产生式规则将所有第一层的节点逐一扩展,得到第二层节点,并逐一检查第二层节点中是否包含目标节点。若没有,再用算符逐一扩展第二层的所有节点……,如此依次扩展,检查下去,直到发现目标节点为止。

出处:https://zhuanlan.zhihu.com/p/322429726

思路与实现

1. 初始定义和数据结构

class PuzzleState {
    int[][] board;         // 棋盘状态
    int emptyX, emptyY;    // 空白格子的坐标
    PuzzleState parent;    // 父状态,用于记录路径

    public PuzzleState(int[][] board, int emptyX, int emptyY, PuzzleState parent) {
        this.board = new int[board.length][board[0].length];
        for (int i = 0; i < board.length; i++) {
            System.arraycopy(board[i], 0, this.board[i], 0, board[0].length);
        }
        this.emptyX = emptyX;
        this.emptyY = emptyY;
        this.parent = parent;
    }

    public boolean isGoal(int[][] targetBoard) {
        return Arrays.deepEquals(this.board, targetBoard);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PuzzleState that = (PuzzleState) o;
        return Arrays.deepEquals(board, that.board);
    }

    @Override
    public int hashCode() {
        return Arrays.deepHashCode(board);
    }
}

PuzzleState 类:用于表示每个状态的棋盘配置。'board' 存储当前的棋盘状态,'emptyX '和 'emptyY '记录空白格子的位置,'parent'记录当前状态的父状态,以便构建路径。

2. 解题方法

solve 方法

public static List<int[][]> solve(int[][] startBoard, int[][] targetBoard) {
    // 寻找起始状态中空白格子的位置
    int startX = 0, startY = 0;
    outerLoop:
    for (int i = 0; i < startBoard.length; i++) {
        for (int j = 0; j < startBoard[0].length; j++) {
            if (startBoard[i][j] == 0) {
                startX = i;
                startY = j;
                break outerLoop;
            }
        }
    }

    // 创建起始状态
    PuzzleState startState = new PuzzleState(startBoard, startX, startY, null);
    Queue<PuzzleState> queue = new LinkedList<>();  // 使用队列进行广度优先搜索
    Set<PuzzleState> visited = new HashSet<>();     // 记录访问过的状态,避免重复访问
    queue.add(startState);
    visited.add(startState);

    int nodeCount = 0;
    long startTime = System.currentTimeMillis();

    while (!queue.isEmpty()) {
        PuzzleState currentState = queue.poll();
        nodeCount++;
        if (currentState.isGoal(targetBoard)) {  // 判断是否达到目标状态
            long endTime = System.currentTimeMillis();
            System.out.println("Solution found in " + (endTime - startTime) + " ms with " + nodeCount + " nodes searched.");
            return constructPath(currentState);  // 构建路径并返回解决方案
        }

        // 尝试移动空白格子的四个方向
        for (int[] direction : DIRECTIONS) {
            int newX = currentState.emptyX + direction[0];
            int newY = currentState.emptyY + direction[1];
            if (newX >= 0 && newX < startBoard.length && newY >= 0 && newY < startBoard[0].length) {
                // 复制当前棋盘状态,并移动空白格子
                int[][] newBoard = new int[startBoard.length][startBoard[0].length];
                for (int i = 0; i < startBoard.length; i++) {
                    System.arraycopy(currentState.board[i], 0, newBoard[i], 0, startBoard[0].length);
                }
                newBoard[currentState.emptyX][currentState.emptyY] = newBoard[newX][newY];
                newBoard[newX][newY] = 0;

                // 创建新状态并检查是否访问过
                PuzzleState newState = new PuzzleState(newBoard, newX, newY, currentState);
                if (!visited.contains(newState)) {
                    queue.add(newState);
                    visited.add(newState);
                }
            }
        }
    }

    // 搜索完成仍未找到解决方案
    long endTime = System.currentTimeMillis();
    System.out.println("No solution found in " + (endTime - startTime) + " ms with " + nodeCount + " nodes searched.");
    return null;  // 返回空表示无解
}

 solve 方法:使用广度优先搜索(BFS)来解决拼图问题。从起始状态开始,逐步尝试所有可能的移动,直到达到目标状态或者所有状态都被搜索过。

3. 解决方案构建

constructPath 方法

private static List<int[][]> constructPath(PuzzleState state) {
    List<int[][]> path = new ArrayList<>();
    while (state != null) {
        path.add(state.board);  // 将每个状态的棋盘配置加入路径
        state = state.parent;   // 往回追溯路径
    }
    Collections.reverse(path);  // 反转路径,使起始状态在最前面
    return path;
}

 使用广度优先搜索算法来解决拼图问题,通过不断尝试所有可能的移动,逐步接近目标状态。每个状态的棋盘配置和移动过程都被封装在 PuzzleState 类中,确保了状态的独立性和可追溯性。解决方案的构建通过回溯父状态的方式实现,最终输出完整的解决路径。

算法可视化

使用stdDraw绘图实现算法的可视化

PuzzlePanelpaintComponent() 方法中,只绘制发生变化的部分,而不是重新绘制整个棋盘。这样可以提升绘制效率和动画的流畅度。同时,我们使用 javax.swing.Timer 来实现动画的更新。在 startAnimation() 方法中,设置一个定时器,每隔一段时间更新一次 PuzzlePanel 的绘制内容,实现逐帧动画效果。此外javax.swing.Timer确保了与Swing的事件分发线程(EDT)兼容,并正确执行UI更新。

 实现效果

已实现的迭代版本

同时保存多个棋盘以供选择

import java.awt.*;
import java.util.*;
import javax.swing.*;

class PuzzleState {
    int[][] board;
    int emptyX, emptyY;
    PuzzleState parent;

    public PuzzleState(int[][] board, int emptyX, int emptyY, PuzzleState parent) {
        this.board = new int[board.length][board[0].length];
        for (int i = 0; i < board.length; i++) {
            System.arraycopy(board[i], 0, this.board[i], 0, board[0].length);
        }
        this.emptyX = emptyX;
        this.emptyY = emptyY;
        this.parent = parent;
    }

    public boolean isGoal(int[][] targetBoard) {
        return Arrays.deepEquals(this.board, targetBoard);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PuzzleState that = (PuzzleState) o;
        return Arrays.deepEquals(board, that.board);
    }

    @Override
    public int hashCode() {
        return Arrays.deepHashCode(board);
    }
}

public class SlidingPuzzleSolver {
    private static final int DIM = 4; // 4x4 board
    private static final int TILE_SIZE = 100; // Size of each tile in pixels
    private static final int BOARD_SIZE = DIM * TILE_SIZE;
    private static final int[][] DIRECTIONS = {
            {1, 0}, {-1, 0}, {0, 1}, {0, -1}
    };

    private int[][] board;
    private java.util.List<int[][]> solutionSteps;
    private JFrame frame;
    private PuzzlePanel puzzlePanel;
    private int stepIndex;
    private javax.swing.Timer animationTimer; // Explicitly use javax.swing.Timer

    public SlidingPuzzleSolver(java.util.List<int[][]> solutionSteps) {
        this.solutionSteps = solutionSteps;
        this.board = solutionSteps.get(0);
        this.stepIndex = 0;

        // Initialize and show GUI
        SwingUtilities.invokeLater(() -> {
            frame = new JFrame("Sliding Puzzle Solver");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            puzzlePanel = new PuzzlePanel();
            frame.add(puzzlePanel);
            frame.pack();
            frame.setVisible(true);

            startAnimation();
        });
    }

    private void startAnimation() {
        animationTimer = new javax.swing.Timer(500, e -> {
            if (stepIndex < solutionSteps.size()) {
                board = solutionSteps.get(stepIndex++);
                puzzlePanel.repaint(); // Trigger repaint to update the puzzle panel
            } else {
                animationTimer.stop();
            }
        });
        animationTimer.setInitialDelay(0);
        animationTimer.start();
    }

    private class PuzzlePanel extends JPanel {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            for (int i = 0; i < DIM; i++) {
                for (int j = 0; j < DIM; j++) {
                    if (board[i][j] != 0) {
                        drawTile(g, j * TILE_SIZE, i * TILE_SIZE, board[i][j]);
                    }
                }
            }
        }

        private void drawTile(Graphics g, int x, int y, int value) {
            g.setColor(Color.LIGHT_GRAY);
            g.fillRect(x, y, TILE_SIZE, TILE_SIZE);
            g.setColor(Color.BLACK);
            g.drawRect(x, y, TILE_SIZE, TILE_SIZE);
            g.setColor(Color.BLACK);
            g.setFont(new Font("Arial", Font.BOLD, 24));
            g.drawString(String.valueOf(value), x + TILE_SIZE / 2 - 10, y + TILE_SIZE / 2 + 10);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(BOARD_SIZE, BOARD_SIZE);
        }
    }

    public static java.util.List<int[][]> solve(int[][] startBoard, int[][] targetBoard) {
        int startX = 0, startY = 0;
        outerLoop:
        for (int i = 0; i < startBoard.length; i++) {
            for (int j = 0; j < startBoard[0].length; j++) {
                if (startBoard[i][j] == 0) {
                    startX = i;
                    startY = j;
                    break outerLoop;
                }
            }
        }

        PuzzleState startState = new PuzzleState(startBoard, startX, startY, null);
        Queue<PuzzleState> queue = new LinkedList<>();
        Set<PuzzleState> visited = new HashSet<>();
        queue.add(startState);
        visited.add(startState);

        int nodeCount = 0;
        long startTime = System.currentTimeMillis();

        while (!queue.isEmpty()) {
            PuzzleState currentState = queue.poll();
            nodeCount++;
            if (currentState.isGoal(targetBoard)) {
                long endTime = System.currentTimeMillis();
                System.out.println("Solution found in " + (endTime - startTime) + " ms with " + nodeCount + " nodes searched.");
                return constructPath(currentState);
            }

            for (int[] direction : DIRECTIONS) {
                int newX = currentState.emptyX + direction[0];
                int newY = currentState.emptyY + direction[1];
                if (newX >= 0 && newX < startBoard.length && newY >= 0 && newY < startBoard[0].length) {
                    int[][] newBoard = new int[startBoard.length][startBoard[0].length];
                    for (int i = 0; i < startBoard.length; i++) {
                        System.arraycopy(currentState.board[i], 0, newBoard[i], 0, startBoard[0].length);
                    }
                    newBoard[currentState.emptyX][currentState.emptyY] = newBoard[newX][newY];
                    newBoard[newX][newY] = 0;
                    PuzzleState newState = new PuzzleState(newBoard, newX, newY, currentState);
                    if (!visited.contains(newState)) {
                        queue.add(newState);
                        visited.add(newState);
                    }
                }
            }
        }

        long endTime = System.currentTimeMillis();
        System.out.println("No solution found in " + (endTime - startTime) + " ms with " + nodeCount + " nodes searched.");
        return null;
    }

    private static java.util.List<int[][]> constructPath(PuzzleState state) {
        java.util.List<int[][]> path = new ArrayList<>();
        while (state != null) {
            path.add(state.board);
            state = state.parent;
        }
        Collections.reverse(path);
        return path;
    }

    public static void main(String[] args) {
        int[][] startBoard = {
                {2, 3, 4, 8},
                {5, 6, 1, 12},
                {9, 10, 7, 0},
                {13, 14, 11, 15}
        };

        int[][] targetBoard = {
                {1, 2, 3, 4},
                {5, 6, 7, 8},
                {9, 10, 11, 12},
                {13, 14, 15, 0}
        };

        java.util.List<int[][]> solution = solve(startBoard, targetBoard);
        if (solution != null) {
            new SlidingPuzzleSolver(solution);
        } else {
            System.out.println("No solution found.");
        }
    }
}

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值