算法与数据结构

幻方问题

2020-08-26  本文已影响0人  凉夜lrs

幻方(Magic Square)是一种将数字安排在正方形格子中,使每行、列和对角线上的数字和都相等的方法。这里简单记录下n阶幻方的生成算法,想要了解更多可以去百度百科。

n阶幻方具体可分为奇数阶幻方,4n阶幻方以及4n+2阶幻方

奇数阶幻方

阶数为奇数的幻方,最经典的填法是罗伯法,步骤可以总结为七言绝句:


image.png

后面两句其实我也没看懂。。。不过没关系,只看前两句照样可以填出幻方,这里稍微解释一下:


image.png
如上图所示(忽略丑感),1填在第一行正中央,然后按阶数n斜填,超顶就回到底,超底就会到顶。填完一斜往下挪一位,元素+1,再重复上述步骤。用Lua代码实现如下:
-- @params number n:n阶方阵的阶数
-- @params table tMatrix:n阶方阵的元素,默认是连续递增的整数
function OddMagicSquare(n, tMatrix)
    if not tMatrix then --不存在则从1开始赋值
        tMatrix = {}
        for nRow = 1, n do
            tMatrix[nRow] = {}
            for nCol = 1, n do
                tMagicSquare[nRow][nCol] = (nRow - 1) * n + nCol    --顺序赋值
            end
        end
    end

    local tMagicSquare = {}
    for nRow = 1, n do
        tMagicSquare[nRow] = {}
    end

    -- 罗伯法
    -- 先填上行正中央,
    -- 依次斜填切莫忘。
    -- 上格没有顶格填,
    -- 顶格没有底格放。
    local nMidCol = (n + 1) / 2
    tMagicSquare[1][nMidCol] = tMatrix[1][1]

    local function _HandleOutData(nValue)
        if nValue < 1 then nValue = n end
        if nValue > n then nValue = 1 end
        return nValue
    end

    local nRow = 1
    local nCol = nMidCol
    local nCount = 1    
    for i = tMatrix[1][2], tMatrix[n][n] do
        nCount = nCount + 1
        if nCount > n then  --n次之后进行下一轮
            nCount = 1
            nRow = nRow + 1
        else
            nRow = nRow - 1
            nCol = nCol + 1
        end

        nRow = _HandleOutData(nRow)
        nCol = _HandleOutData(nCol) 
        tMagicSquare[nRow][nCol] = i
    end 

    return tMagicSquare
end

为什么要传一个矩阵参数?当然是为了后面做准备。

4n阶幻方

即阶数为4的倍数。这类幻方可由海尔法得到,有两个步骤:

1. 先把数字按顺序填。然后,按4×4分割
2. 每个小方阵对角线上的数字换成,大方阵中和它中心对称的位置的数

挺简单明了的,具体的图就不贴了,可以在后面的参考中看,有具体的图。
Lua实现如下:

-- 海尔法:
-- 先把数字按顺序填。然后,按4×4分割
-- 每个小方阵对角线上的数字换成,大方阵中和它中心对称的位置的数

-- @params number n:n阶方阵的阶数
function DoubleEvenMagicSquare(n)
    local tMagicSquare = {}
    for nRow = 1, n do
        tMagicSquare[nRow] = {}
        for nCol = 1, n do
            tMagicSquare[nRow][nCol] = (nRow - 1) * n + nCol    --顺序赋值
        end
    end
    
    local nSum = n*n + 1    --顺序排列且中心对称,相加等于定值
    local nRow, nCol
    for i=1,n,4 do
        for j=1,n,4 do
            for k=0,3 do
                nRow = i+k
                nCol = j+k
                tMagicSquare[nRow][nCol] = nSum - tMagicSquare[nRow][nCol]
                nCol = j+3-k
                tMagicSquare[nRow][nCol] = nSum - tMagicSquare[nRow][nCol]
            end
        end
    end
    
    return tMagicSquare
end

4n+2阶幻方

阶数为偶数且非四的倍数,算是最难处理的一种了。可以用斯特拉兹法生成,分为三个步骤:

1. 分为四个同阶的奇数阶,由小到大依次为上左子阵(i),下右子阵(i+v),上右子阵(i+2v),下左子阵(i+3v),4个子方阵对应位置元素相差v,其中v=n*n/4,然后按罗伯法处理。
2. 上左、下左两个方阵,从中间行、中间格开始,按自左向右的方向,标出K格;其它行则标出最左边的K格。交换两个方阵标出来的数字。如图:
image.png
3. 上右、下右两个方阵,从中间列开始,自右向左,标出K-1格(K等于1不作处理)交换两个方阵标出来的数字。如图:
image.png

Lua代码实现如下:

-- 斯特拉兹法

-- 1.分为四个同阶的奇数阶,由小到大依次为上左子阵(i),下右子(i+v),上右子阵(i+2v),下左子阵(i+3v),
-- 即4个子方阵对应元素相差v,其中v=n*n/4。然后按罗伯法处理
-- 2.4K+2阶幻方,求出K。
-- 3.上左、下左两个方阵,从中间行、中间格开始,按自左向右的方向,标出K格;其它行则标出最左边的K格。交换两个方阵标出来的数字
-- 4.上右、下右两个方阵,从中间列开始,自右向左,标出K-1格。交换两个方阵标出来的数字
function SingleEvenMagicSquare(n)
    local tMagicSquare1 = {}
    local tMagicSquare2 = {}
    local tMagicSquare3 = {}
    local tMagicSquare4 = {}

    local nSubLen = n/2
    local nDiffValue = n*n/4
    local nFirstMatrixValue
    for nRow=1,nSubLen do
        tMagicSquare1[nRow] = {}
        tMagicSquare2[nRow] = {}
        tMagicSquare3[nRow] = {}
        tMagicSquare4[nRow] = {}
        for nCol=1,nSubLen do
            nFirstMatrixValue = (nRow - 1) * nSubLen + nCol
            tMagicSquare1[nRow][nCol] = nFirstMatrixValue
            tMagicSquare2[nRow][nCol] = nFirstMatrixValue + 2*nDiffValue
            tMagicSquare3[nRow][nCol] = nFirstMatrixValue + 3*nDiffValue
            tMagicSquare4[nRow][nCol] = nFirstMatrixValue + nDiffValue
        end
    end

    tMagicSquare1 = OddMagicSquare(nSubLen, tMagicSquare1)
    tMagicSquare2 = OddMagicSquare(nSubLen, tMagicSquare2)
    tMagicSquare3 = OddMagicSquare(nSubLen, tMagicSquare3)
    tMagicSquare4 = OddMagicSquare(nSubLen, tMagicSquare4)

    local nKey = (n-2)/4
    local nSubMidSide = (nSubLen + 1) / 2
    local nTemp
    -- 步骤3
    for nCol = nSubMidSide, nSubMidSide + nKey - 1 do
        nTemp = tMagicSquare1[nSubMidSide][nCol]
        tMagicSquare1[nSubMidSide][nCol] = tMagicSquare3[nSubMidSide][nCol]
        tMagicSquare3[nSubMidSide][nCol] = nTemp
    end
    for nRow = 1, nSubLen do
        if nRow ~= nSubMidSide then
            for nCol = 1, nKey do
                nTemp = tMagicSquare1[nRow][nCol]
                tMagicSquare1[nRow][nCol] = tMagicSquare3[nRow][nCol]
                tMagicSquare3[nRow][nCol] = nTemp
            end
        end
    end
    -- 步骤4
    if nKey > 1 then
        for nRow = 1, nSubLen do
            for nCol = nSubMidSide, nSubMidSide - (nKey - 2), -1 do
                nTemp = tMagicSquare2[nRow][nCol]
                tMagicSquare2[nRow][nCol] = tMagicSquare4[nRow][nCol]
                tMagicSquare4[nRow][nCol] = nTemp
            end
        end
    end

    local tMagicSquare = {}
    for nRow = 1, n do
        tMagicSquare[nRow] = {}
        for nCol = 1, n do
            if nRow <= nSubLen and nCol <= nSubLen then
                tMagicSquare[nRow][nCol] = tMagicSquare1[nRow][nCol]
            elseif nRow <= nSubLen and nCol > nSubLen then
                tMagicSquare[nRow][nCol] = tMagicSquare2[nRow][nCol - nSubLen]
            elseif nRow > nSubLen and nCol <= nSubLen then
                tMagicSquare[nRow][nCol] = tMagicSquare3[nRow - nSubLen][nCol]
            else
                tMagicSquare[nRow][nCol] = tMagicSquare4[nRow - nSubLen][nCol - nSubLen]
            end
        end
    end
    
    return tMagicSquare
end

上面OddMagicSquare的矩阵参数就是给这里用的。

参考

由n阶幻方问题引发的思考
【算法导论】幻方算法

上一篇下一篇

猜你喜欢

热点阅读