Java实现数字华容道:乱序算法

实现“数字华容道(N-Puzzle,如 8 数码 / 15 数码)”时,乱序算法是核心
👉 既要“打乱”,又要保证一定可解(这是很多人踩坑的点)。

下面我从原理 → 正确算法 → Java实现 → 进阶优化,帮你一次搞懂 👇


一、为什么不能随便乱序?

如果你直接随机打乱数组:

Collections.shuffle(list);

👉 问题:
有一半概率是无解的!

这是因为数字华容道存在一个关键数学性质:

👉 逆序数(Inversion Count)


二、可解性的核心规则(必须掌握)

以最常见的 3×3(8数码) 为例:

👉 判断规则:

  • 将数组按一维展开(忽略 0)
  • 计算逆序数
  • 逆序数为偶数 → 可解
  • 逆序数为奇数 → 无解

🔍 什么是逆序数?

例子:

1 2 3
4 5 6
8 7 0

展开:

1 2 3 4 5 6 8 7

👉 因为 8 在 7 前面 → 1 个逆序
👉 逆序数 = 1(奇数)→ ❌ 无解


三、正确乱序方法(推荐3种)


✅ 方法一:随机打乱 + 修正(最常用)

思路:

  1. 随机打乱
  2. 判断是否可解
  3. 如果不可解 → 交换两个数修正

Java 实现

import java.util.*;

public class PuzzleShuffle {

    public static int[] generatePuzzle(int size) {
        int n = size * size;
        List<Integer> list = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            list.add(i);
        }

        Collections.shuffle(list);

        int[] arr = list.stream().mapToInt(i -> i).toArray();

        if (!isSolvable(arr, size)) {
            // 交换两个非0元素
            for (int i = 0; i < arr.length - 1; i++) {
                if (arr[i] != 0 && arr[i + 1] != 0) {
                    int temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                    break;
                }
            }
        }

        return arr;
    }

    // 判断是否可解
    public static boolean isSolvable(int[] arr, int size) {
        int inv = countInversions(arr);

        if (size % 2 == 1) {
            return inv % 2 == 0;
        } else {
            int row = findZeroRow(arr, size);
            return (inv + row) % 2 == 0;
        }
    }

    private static int countInversions(int[] arr) {
        int inv = 0;
        for (int i = 0; i < arr.length; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[i] != 0 && arr[j] != 0 && arr[i] > arr[j]) {
                    inv++;
                }
            }
        }
        return inv;
    }

    private static int findZeroRow(int[] arr, int size) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == 0) {
                return i / size;
            }
        }
        return 0;
    }
}


✅ 方法二:从“已解状态”随机移动(强烈推荐🔥)

👉 最安全、最真实的方式

思路:

  1. 从目标状态开始
  2. 随机移动空格(上下左右)
  3. 执行 N 次

👉 100% 可解!


Java 实现

import java.util.*;

public class PuzzleShuffle2 {

    static int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};

    public static int[][] shuffle(int size, int steps) {
        int[][] board = new int[size][size];

        int val = 1;
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                board[i][j] = val % (size * size);
                val++;
            }
        }

        int x = size - 1, y = size - 1; // 空格位置
        Random rand = new Random();

        for (int i = 0; i < steps; i++) {
            List<int[]> validMoves = new ArrayList<>();

            for (int[] d : dirs) {
                int nx = x + d[0];
                int ny = y + d[1];

                if (nx >= 0 && nx < size && ny >= 0 && ny < size) {
                    validMoves.add(new int[]{nx, ny});
                }
            }

            int[] move = validMoves.get(rand.nextInt(validMoves.size()));

            board[x][y] = board[move[0]][move[1]];
            board[move[0]][move[1]] = 0;

            x = move[0];
            y = move[1];
        }

        return board;
    }
}


✅ 方法三:A* 逆向生成(高阶)

👉 用搜索算法生成“难度可控”的局面
(一般用于 AI / 游戏难度控制)


四、三种方法对比

方法是否可解随机性推荐程度
随机+修正⭐⭐⭐⭐
随机移动中(但真实)⭐⭐⭐⭐⭐
A*生成可控⭐⭐⭐

五、实战建议(非常重要)

👉 做项目建议用:

✅ 方法二(随机移动)

原因:

  • 100%可解
  • 更符合玩家体验
  • 不会出现“奇怪状态”

六、进阶优化方向(面试加分🔥)

如果你想做到“精通”,可以继续深入:

⭐ 1. 难度控制

  • 控制移动步数(steps)
  • 越大 → 越难

⭐ 2. 避免“来回走”

// 记录上一步,避免回退


⭐ 3. 加入 AI 求解(A*算法)

  • 曼哈顿距离
  • IDA*

七、总结一句话

👉 数字华容道乱序本质:

“在保证可解的前提下制造随机状态”


如果你愿意,我可以帮你继续做👇
✅ 完整项目(GUI版数字华容道)
✅ A* 自动求解器
✅ 或“面试级算法讲解(含最优解路径)”

文章已创建 5186

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部