您的位置 首页 java

JAVA实现幻方

幻方 (Magic Square)是一种将数字安排在正方形格子中,使每行、列和对角线上的数字和都相等的方法。

幻方也是一种中国传统游戏。旧时在官府、学堂多见。它是将从一到若干个数的自然数排成纵横各为若干个数的正方形,使在同一行、同一列和同一对角线上的几个数的和都相等。

三阶幻方

本篇主聊高阶幻方 构造方法 的java实现

数据结构:以二维数组存放数字

例:上面的三阶幻方数组数据如下(不考虑幻方阶次较高,最大值超过int最大值)

int[][] square = {
{8,1,6},
{3,5,7},
{4,9,2}
};
 

数据结构有了,怎么去验证一个二阶数组的数据是不是一个有效的幻方构造数据呢?

具体思路就是反正法,默认数据符合幻方数据内容:

/**验证一个二阶数组里的数是不是符合一个幻方的要求*/public static boolean isValidSquare(int[][] nums){
try {
//i阶次幻方
int i = nums.length;
//行列斜的和为
int s = (i*i+1)*i/2;
//数据完整性校验数组
int[]  check  = new int[i*i+1];
//校验每一行/每一列的和是不是等于s
for (int x = 0; x < i; x++) {
int lineSum = 0;
int colSum = 0;
for (int y = 0; y < i; y++) {
check[nums[x][y]]=nums[x][y];
lineSum += nums[x][y];
colSum += nums[y][x];
}
if (lineSum!=s || colSum!=s) {
return false;
}
}
//校验两个对角线的和是否等于s
int s1Sum = 0;
int s2Sum = 0;
for (int x = 0; x < i; x++) {
s1Sum += nums[x][x];
s2Sum += nums[i-x-1][x];
}
if (s1Sum!=s || s2Sum!=s) {
return false;
}
//校验check数组中是否包含1到i*i中的每一个数
for (int j = 0; j < check.length; j++) {
if (j!=check[j]) {
return false;
}
}
} catch (Exception e) {
e.printStackTrace();
//默认符合幻方规则,任意数组越界等错误都认为是不合格的数据
return false;
}
return true;
}
 

下面开讲n(>=3)阶幻方的构造及实现

奇数阶幻方

拉-卢贝尔 算法

这个算法又称“阶梯法”。算法如下:

  1. 将1置于第一行中间。
  2. 将下一个数字置于当前数字的右上角。如果已经处于方阵的边界,则放在方阵的对边(如图1中的2和4)。
  3. 若出现下面两种情况,则将下一个数字放于当前数字的下方:

①当前位置的右上角已经有一个数字(如图2中的6和11)。

②当前位置已经是方阵的右上方(如图2中的16)。

  1. 结束,如下图3:

Java实现:

/**
 * k=2*n+1
 * 拉-卢贝尔算法
 * 阶梯法
 */public static int[][] getSquare2k1LenF1(int k) throws Exception{
int[][] res = new int[k][k];
int x = 0;
int y = k/2;
int total = k*k;
for (int i = 1; i <= total; i++) {
res[x][y] = i;
int m = (x-1+k)%k;
int n = (y+1)%k;
if (res[m][n]>0) {
x = (x+1)%k;
}else{
x= m;
y= n;
}
}
return res;
}
 

菱形算法

另一种由康韦(J.H.Conway)建立的算法被称为“菱形算法”,步骤如下(以5×5为例):

  1. 从左边中间开始,将奇数在方阵内填成一个菱形。
  2. 将方阵分成5条对角线,每条对角线上有5个方格。如果图1所示。
  3. 从第一条对角线开始将偶数填入剩余的空格内,图2中填满了前两条对角线。
  4. 结束,如图3。

再给一个7阶的构造填数过程,具体规则大家自己揣摩

Java实现:

/**
 * 菱形算法
 * k=2*n+1
 */public static int[][] getSquare2k1LenF2(int k) throws Exception{
int[][] res = new int[k][k];
//初始化奇数空间
int n = k/2;
//奇数起点坐标
int baseX = n,baseY = 0;
//起点走向,true向右 false向下
boolean type = true;
//奇数计数
int count = 1;
//当前奇数坐标
int x = baseX;
int y = baseY;
while(true){
res[x][y] = count;
count = count+2;
x--;y++;
if (y>=x-n && y<=x+n) {
}else{
if (type) {
baseY++;
}else{
baseX++;
}
type = !type;
if (baseX==k-1 && baseY==n+1) {
 break ;
}
x = baseX;
y = baseY;
}
}
//偶数起点坐标
int baseP = -1;
int baseQ = n+1;
//起点走向,true向右 false向下
type = false;
//偶数计数
count = 2;
//当前奇数坐标
int p = baseP;
int q = baseQ;
while(true){
p = (p+k)%k;
q = (q+k)%k;
res[p][q] = count;
count = count+2;
p = (p-1+k)%k;
q = (q+1)%k;
if (res[p][q]!=0) {
if (type) {
baseQ++;
}else{
baseP++;
}
type = !type;
if (baseP==n && baseQ==k) {
break;
}
p = baseP;
q = baseQ;
}
}
return res;
}
 

杨辉法

九子斜排 上下对易 左右相更 四维挺出 戴九履一 左三右七 二四为肩 六八为足

举个例子:9个数斜着排,上下的两个数1,9,左右的两个数3,7互相换一下,四个角上的2,4,6,8就移到那四个角上去,这样就填好了一个三阶幻方了.

再给一个7阶的构造填数过程,具体规则大家自己揣摩

Java实现:

/**
 * 九子斜排 上下对易 左右相更 四维挺出 戴九履一 左三右七 二四为肩 六八为足
 * k=2*n+1
 */public static int[][] getSquare2k1LenF3(int k) throws Exception{
int[][] res = new int[k][k];
//初始构造一个菱形空间
int[][] dia = new int[2*k-1][2*k-1];
//把1到k*k个数填入菱形空间
for (int i = 1; i <= k*k; i++) {
//计算i在菱形空间的坐标
//批次
int p = (i+(k-1))/k;
//偏移量
int q = i%k==0?k:i%k;
q--;
dia[p-1+q][k-p+q] = i;
}
//填充中心
int m = (k-1)/2;
for (int i = m; i <m+k ; i++) {
for (int j = m; j < m+k; j++) {
if (dia[i][j]==0) {
if (i<j) {
if(i+j<2*k-1){
dia[i][j] = dia[i+k][j];
}else{
dia[i][j] = dia[i][j-k];
}
}else{
if (i+j<2*k-1) {
dia[i][j] = dia[i][j+k];
}else{
dia[i][j] = dia[i-k][j];
}
}
}
//往结果集赋值
res[i-m][j-m] = dia[i][j];
}
}
return res;
}
 

单偶数阶幻方

侓克斯算法

这个算法也是由康韦给出的。思想是将方阵分成多个2×2的小方阵,小方阵按照位置分成L、U、X三种类型。然后在大体上按照卢-拉贝尔算法来走,在每个小方阵中根据小方阵的类型来填数。具体算法如下:

  1. 将方阵分成(2i+1)个(2×2)的小方阵,小方阵的分类这样确定:前i+1行是L类型,后面一行是U类型,最后的i-1行是X类型,然后交换第i+1行和第i+2行中间小方阵的类型。对于10×10的方阵如图1。
  2. L、U、X的填法如图2。
  3. 最终结果如图3。

Java实现:

/**
 * k=4*n+2
 * 侓克斯算法
 * 
 * L: 4 1 U: 1 4 X: 1 4
 * 2 3 2 3 3 2
 * 
 * 前i+1行是L类型,后面一行是U类型,最后的i-1行是X类型,然后交换第i+1行和第i+2行中间小方阵的类型
 */public static int[][] getSquare4k2LenF1(int k) throws Exception{
int[][] res = new int[k][k];
int x = 0;
int y = k/2-1;
int total = k*k/4;
for (int i = 1; i <= total; i++) {
//定型
//1:L 0:U -1:X
int type = 1;
if ((x== k/2+1 && y!=k/2-1) || (x==k/2-1 && y==k/2-1)) {
type = 0;
}
if (x>= k/2+3) {
type = -1;
}
//填充
switch (type) {
case 1:
res[x][y] = 4*i;
res[x][y+1] = 4*i-3;
res[x+1][y] = 4*i-2;
res[x+1][y+1] = 4*i-1;
break;
case 0:
res[x][y] = 4*i-3;
res[x][y+1] = 4*i;
res[x+1][y] = 4*i-2;
res[x+1][y+1] = 4*i-1;
break;
default:
res[x][y] = 4*i-3;
res[x][y+1] = 4*i;
res[x+1][y] = 4*i-1;
res[x+1][y+1] = 4*i-2;
break;
}
//定位
int m = (x-2+k)%k;
int n = (y+2)%k;
if (res[m][n]>0) {
x = (x+2)%k;
}else{
x= m;
y= n;
}
}
 

return res;

}

加法算法

将一个幻方加上另外一个幻方所得的和仍然具有幻方的特性,只是可能会有重复项,这是幻方的加法特性。下面的方法就是根据这个特性设计的,首先建立两个方阵A、B,具有幻方的特性(横、纵、斜和相同),然后让A加上B的i倍,就得到一个幻方。假如我们要作一个4i+2阶幻方(此处以14为例)。具体算法如下:

先作一个14(4i+2)阶的方阵A,这个方阵分成4个7(2i+1)阶小方阵,每个小方阵是一个奇数阶的幻方,奇数阶幻方构造方法已经有了。如图1。

  1. 先作一个14(4i+2)阶的方阵A,这个方阵分成4个7(2i+1)阶小方阵,每个小方阵是一个奇数阶的幻方,奇数阶幻方构造方法已经有了。如图1。
  2. 再作一个14(4i+2)阶的方阵B,这个方阵只由0、1、2、3构成,具体作法如下:

①第一列:3(i)个3,4(i+1)个0,5(i+2)个2,2(i-1)个1

②前7(2i+1)列与都与第一列相同,只有第4(i+1)列例外,该列第一个3和第一 个0交换位置。

③后7(2i+1)列与前7(2i+1)列相同,不过3和0交换,1和2交换。

④结果如图2。

  1. 构造幻方C=A+i2B。如图3,C即所求。

Java实现:

/**
 * 加法算法
 * k=4*n+2
 */public static int[][] getSquare4k2LenF2(int k) throws Exception{
int[][] res = new int[k][k];
//幻方A初始化
int[][] sA = new int[k][k];
int[][] temp = getSquare2k1LenF1(k/2);
for (int i = 0; i < k; i++) {
for (int j = 0; j < k; j++) {
sA[i][j] = temp[i%(k/2)][j%(k/2)];
}
}
//幻方B初始化
int[][] sB = new int[k][k];
int n = (k-2)/4;
for (int i = 0; i < k; i++) {
for (int j = 0; j < k; j++) {
if (i<k/2 && j<n || i>=k/2 && j>=n && j<k/2) {
sB[i][j]=3;
}
if(i<k/2 && j>=k/2 && j<3*n+3 || i>=k/2 && j>=3*n+3){
sB[i][j]=2;
}
if (i<k/2 && j>=3*n+3 || i>=k/2 && j>=k/2 && j<3*n+3) {
sB[i][j]=1;
}
}
}
sB[n][0] = 0;
sB[n][n] = 3;
sB[3*n+1][0] = 3;
sB[3*n+1][n] = 0;
//幻方A和幻方B相加
//RES = A + B*k*k/4
for (int i = 0; i < k; i++) {
for (int j = 0; j < k; j++) {
res[i][j] = sA[i][j]+sB[i][j]*k*k/4;
}
}
return res;
}
 

双偶数阶幻方

分割算法

  1. 将方阵分成16个小方阵,如图1。
  2. 先在A、C、E、G、I方阵中填入数字,其他方阵跳过,如图2。
  3. 再逆序(从右下往左上)赶往余下的数字,如图3。
  4. 结束,如图3

下面是一个8次的方阵:

Java实现:
/**
 * k=4*n
 * 分割算法
 */public static int[][] getSquare4kLenF1(int k) throws Exception{
int[][] res = new int[k][k];
//分割数据临时数组
int[] temp = new int[k*k/2];
int index = 0;
//预填充
for (int i = 0; i < k; i++) {
for (int j = 0; j < k; j++) {
//符合分割条件
int m = i%4;
int n = j%4;
if (m!=n && m+n!=3) {
temp[index++] = i*k+j+1;
res[i][j]= 0;
}else{
res[i][j] = i*k+j+1;
}
}
}
//分割出的数据排序
Arrays.sort(temp);
int tempIndex = temp.length-1;
//再填充
for (int i = 0; i < k; i++) {
for (int j = 0; j < k; j++) {
if (res[i][j]==0) {
res[i][j] = temp[tempIndex--];
}
}
}
return res;
}
 

对角线算法

  1. 将数字顺序填入方阵内,如图1。
  2. 将方阵分成四个相同大小的方阵。并找出每个小方阵的对角线,如图1阴影部分。
  3. 将阴影部分旋转180度,如图2。

Java实现:

/**
 * 对角线算法
 * k=4*n
 */public static int[][] getSquare4kLenF2(int k) throws Exception{
int[][] res = new int[k][k];
for (int i = 0; i < k; i++) {
for (int j = 0; j < k; j++) {
//坐标i,j是否处在对角线规则内
int m = i%4;
int n = j%4;
if (m==n || m+n==3) {
res[i][j] = (k-1-i)*k+(k-1-j)+1;
}else{
res[i][j] = i*k+j+1;
}
}
}
return res;
}
 

文章来源:智云一二三科技

文章标题:JAVA实现幻方

文章地址:https://www.zhihuclub.com/186115.shtml

关于作者: 智云科技

热门文章

网站地图