2. 什么是魔方阵?
魔方阵是一个数学谜题。我们从一个 n×n 的方阵开始,需要用数字填充它,使得每个数字在 1 到 n² 之间且仅出现一次,并且每一行、每一列以及两条对角线的和都相等。
例如,一个 3×3 的魔方阵可能是:
可以看到,每个单元格包含不同的数字(1 到 9),且每行、每列以及对角线的和都是 15。
这里还有一个额外规律:所有行、列、对角线的和等于一个仅由 n 计算出的值。具体公式为:
对于 3×3 方阵,计算结果是 (3³ + 3)/2 = 15。
根据方阵大小的不同,有三种相对简单的生成算法:
- 奇数阶(每边单元格数为奇数)
- 双偶阶(每边单元格数是 4 的倍数)
- 单偶阶(每边单元格数是 2 的倍数但不是 4 的倍数)
接下来我们将详细探讨这些算法及其 Java 实现。
3. 验证魔方阵
在生成魔方阵之前,我们需要先能验证给定方阵是否符合要求:即所有行、列和对角线的和相等。
首先定义一个表示方阵的类:
public class MagicSquare {
private int[][] cells;
public MagicSquare(int n) {
this.cells = new int[n][];
for (int i = 0; i < n; ++i) {
this.cells[i] = new int[n];
}
}
public int getN() {
return cells.length;
}
public int getCell(int x, int y) {
return cells[x][y];
}
public void setCell(int x, int y, int value) {
cells[x][y] = value;
}
}
这只是对二维 int 数组的简单封装,确保数组大小正确并提供便捷的单元格访问方法。
现在编写验证魔方阵的方法:
首先计算期望的和值:
int n = getN();
int expectedValue = ((n * n * n) + n) / 2;
然后验证对角线:
// 对角线验证
if (IntStream.range(0, n).map(i -> getCell(i, i)).sum() != expectedValue) {
throw new IllegalStateException("主对角线和不符合预期值");
}
if (IntStream.range(0, n).map(i -> getCell(i, n - i - 1)).sum() != expectedValue) {
throw new IllegalStateException("副对角线和不符合预期值");
}
接着验证行和列:
// 行验证
IntStream.range(0, n).forEach(y -> {
if (IntStream.range(0, n).map(x -> getCell(x, y)).sum() != expectedValue) {
throw new IllegalStateException("某行和不符预期值");
}
});
// 列验证
IntStream.range(0, n).forEach(x -> {
if (IntStream.range(0, n).map(y -> getCell(x, y)).sum() != expectedValue) {
throw new IllegalStateException("某列和不符预期值");
}
});
如果任何验证失败,抛出异常表示方阵无效。
4. 生成魔方阵
现在我们已经能验证魔方阵,接下来实现生成算法。根据方阵大小不同,使用三种算法:
4.1 奇数阶方阵算法
第一种算法适用于每边单元格数为奇数的方阵。
生成规则:
- 将数字 1 放在顶行中间单元格
- 后续数字放置规则:
- 尝试放在当前单元格的右上单元格(超出边界时循环到另一侧)
- 若目标单元格已被占用,则放在当前单元格的正下方(同样处理边界)
例如 3×3 方阵:
- 起始位置:顶行中间
- 右上移动(循环到底行右列)
- 再次右上移动到中间左列
- 再次右上会回到起始位置,因此改为下移到底行左列
重复此过程直到填满所有单元格。
4.2 奇数阶方阵实现
Java 实现步骤:
- 放置起始数字:
int y = 0;
int x = (n - 1) / 2;
setCell(x, y, 1);
- 循环放置后续数字:
for (int number = 2; number <= n * n; ++number) {
int nextX = ...;
int nextY = ...;
setCell(nextX, nextY, number);
x = nextX;
y = nextY;
}
- 计算下一个位置:
- 优先尝试右上移动(处理边界循环):
int nextX = x + 1; if (nextX == n) { nextX = 0; } int nextY = y - 1; if (nextY == -1) { nextY = n - 1; }
- 若目标单元格被占用,改为下移:
if (getCell(nextX, nextY) != 0) { nextX = x; nextY = y + 1; if (nextY == n) { nextY = 0; } }
- 优先尝试右上移动(处理边界循环):
完整实现可生成任意奇数阶魔方阵。例如 9×9 结果:
4.3 双偶阶方阵算法
奇数阶算法不适用于偶数阶。双偶阶指边长是 4 的倍数(如 4×4, 8×8)。
生成步骤:
完成后即得到有效魔方阵。
4.4 双偶阶方阵实现
Java 实现技巧:
- 单轮填充优化:根据是否标记区域决定递增或递减赋值:
int number = 1; for (int y = 0; y < n; ++y) { for (int x = 0; x < n; ++x) { boolean highlighted = ...; if (highlighted) { setCell(x, y, (n * n) - number + 1); } else { setCell(x, y, number); } number += 1; } }
- 判断标记区域:
if ((y < n/4 || y >= 3*n/4) && (x >= n/4 && x < 3*n/4)) { highlighted = true; } else if ((x < n/4 || x >= 3*n/4) && (y >= n/4 && y < 3*n/4)) { highlighted = true; }
完整实现可生成任意双偶阶魔方阵。例如 8×8 结果:
4.5 单偶阶方阵算法
单偶阶指边长是 2 的倍数但不是 4 的倍数(最小为 6×6,因 2×2 无解)。
生成步骤:
- 将方阵分为四个奇数阶子方阵:
- 按奇数阶算法填充各子方阵,但分配不同数字范围:
- 左上:1 到 (n/2)²
- 右下:(n/2)² + 1 到 2×(n/2)²
- 右上:2×(n/2)² + 1 到 3×(n/2)²
- 左下:3×(n/2)² + 1 到 n²
- 此时列和正确,但行和对角线不正确。需交换上下半部分单元格:
- 左上子方阵顶行左侧 n/4 个单元格
- 左上子方阵底行左侧 n/4 个单元格
- 中间行:从第二列开始交换 n/4 个单元格
- 右上子方阵:从右侧开始交换 n/4 - 1 个单元格
交换后得到有效魔方阵(本例中所有和为 505)。
4.6 单偶阶方阵实现
基于奇数阶算法实现:
- 计算关键值:
int halfN = n/2; int swapSize = n/4; // 自动向下取整
- 填充四个子方阵(假设已有奇数阶填充方法):
populateOddArea(0, 0, halfN, 0); populateOddArea(halfN, halfN, halfN, halfN * halfN); populateOddArea(halfN, 0, halfN, (halfN * halfN) * 2); populateOddArea(0, halfN, halfN, (halfN * halfN) * 3);
- 执行单元格交换:
- 左侧区域交换:
for (int x = 0; x < swapSize; ++x) { swapCells(x, 0, x, halfN); // 顶行 swapCells(x, halfN - 1, x, n - 1); // 底行 // 中间行 for (int y = 1; y < halfN - 1; ++y) { swapCells(x + 1, y, x + 1, y + halfN); } }
- 右侧区域交换:
for (int x = 0; x < swapSize - 1; ++x) { for (int y = 0; y < halfN; ++y) { swapCells(n - x - 1, y, n - x - 1, y + halfN); } }
- 左侧区域交换:
完整实现可生成任意单偶阶魔方阵。例如 10×10 结果:
5. 总结
我们探讨了魔方阵的生成算法及其 Java 实现。关键点包括: ✅ 三种算法分别处理奇数阶、双偶阶和单偶阶方阵 ✅ 验证逻辑确保生成结果正确性 ⚠️ 单偶阶算法最复杂,需注意单元格交换规则
完整代码可参考 GitHub 仓库。