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. 将数字 1 放在顶行中间单元格
  2. 后续数字放置规则:
    • 尝试放在当前单元格的右上单元格(超出边界时循环到另一侧)
    • 若目标单元格已被占用,则放在当前单元格的正下方(同样处理边界)

例如 3×3 方阵:

  • 起始位置:顶行中间
  • 右上移动(循环到底行右列)
  • 再次右上移动到中间左列
  • 再次右上会回到起始位置,因此改为下移到底行左列

移动示例

重复此过程直到填满所有单元格。

4.2 奇数阶方阵实现

Java 实现步骤:

  1. 放置起始数字:
int y = 0;
int x = (n - 1) / 2;
setCell(x, y, 1);
  1. 循环放置后续数字:
for (int number = 2; number <= n * n; ++number) {
    int nextX = ...;
    int nextY = ...;

    setCell(nextX, nextY, number);

    x = nextX;
    y = nextY;
}
  1. 计算下一个位置:
    • 优先尝试右上移动(处理边界循环):
      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 结果:

9×9 奇数阶魔方阵

4.3 双偶阶方阵算法

奇数阶算法不适用于偶数阶。双偶阶指边长是 4 的倍数(如 4×4, 8×8)。

生成步骤:

  1. 标记四个特殊区域(距离边缘 n/4 且距离角落超过 n/4 的单元格): 特殊区域标记
  2. 两轮填充:
    • 第一轮:从左上到右下,仅填充未标记区域(按 1 到 n² 顺序): 第一轮填充
    • 第二轮:从右下到左上,仅填充标记区域(按 n² 递减顺序): 第二轮填充

完成后即得到有效魔方阵。

4.4 双偶阶方阵实现

Java 实现技巧:

  1. 单轮填充优化:根据是否标记区域决定递增或递减赋值:
    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;
        }
    }
    
  2. 判断标记区域:
    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 结果:

8×8 双偶阶魔方阵

4.5 单偶阶方阵算法

单偶阶指边长是 2 的倍数但不是 4 的倍数(最小为 6×6,因 2×2 无解)。

生成步骤:

  1. 将方阵分为四个奇数阶子方阵: 四分区示意图
  2. 按奇数阶算法填充各子方阵,但分配不同数字范围:
    • 左上:1 到 (n/2)²
    • 右下:(n/2)² + 1 到 2×(n/2)²
    • 右上:2×(n/2)² + 1 到 3×(n/2)²
    • 左下:3×(n/2)² + 1 到 n²
  3. 此时列和正确,但行和对角线不正确。需交换上下半部分单元格:
    • 左上子方阵顶行左侧 n/4 个单元格
    • 左上子方阵底行左侧 n/4 个单元格
    • 中间行:从第二列开始交换 n/4 个单元格
    • 右上子方阵:从右侧开始交换 n/4 - 1 个单元格

单元格交换示例

交换后得到有效魔方阵(本例中所有和为 505)。

4.6 单偶阶方阵实现

基于奇数阶算法实现:

  1. 计算关键值:
    int halfN = n/2;
    int swapSize = n/4;  // 自动向下取整
    
  2. 填充四个子方阵(假设已有奇数阶填充方法):
    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);
    
  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 结果:

10×10 单偶阶魔方阵

5. 总结

我们探讨了魔方阵的生成算法及其 Java 实现。关键点包括: ✅ 三种算法分别处理奇数阶、双偶阶和单偶阶方阵 ✅ 验证逻辑确保生成结果正确性 ⚠️ 单偶阶算法最复杂,需注意单元格交换规则

完整代码可参考 GitHub 仓库


原始标题:Creating a Magic Square in Java