为什么我的Magic Square算法只能找到一半可能的解决方案?

时间:2016-03-16 00:44:04

标签: c++ arrays algorithm function backtracking

Magic Square是一个二维数组,必须以某种方式填充1 to n^2中的数字,以便所有行,列和对角线的总和必须为(n*(n^2 + 1) / 2)。所以在3x3阵列中它是3*(3*3 + 1) / 2) = 3*10 / 2 = 15,在4x4阵列中它是4*17 / 2 = 34等。 如果我没记错的话,3x3有8个可能的解决方案,4x4有7000个以上,5x5有数十亿个。 我的任务是在合理的时间内找到所有可能的解决方案。 3x3是即时8解决方案。然而,在4x4中,我只能在17到20秒内从7000+解决方案中获得3456(如果我将打印移除到屏幕,则为6+)。

#include <iostream>
#include <windows.h>
#include <ctime>

using namespace std;

//n is the size of the square
//k is n^2
int n = 0, k = 0;

//nr is the number of solutions
unsigned int nr = 0;

//The magic Square
int y[20][20];

bool member[20];
bool dbug = false;

//SUM of the ROW is (n*(n*n + 1) / 2)
bool rowAccept(int p) {
    int sum = 0;

    for (int i = 1; i <= n; i++)
        sum += y[p][i];

    return (sum == (n*(k + 1) / 2));
}

//SUM of the COLUMN is (n*(n*n + 1) / 2)
bool colAccept(int i, int j) {
    int sum = 0;

    for (int p = 1; p <= n - 1; p++)
        sum += y[p][i];

    sum += j;

    return (sum == (n*(k + 1) / 2));
}

/*
x x y y
x x y y
z z t t
z z t t

Each of these sub squares will have the SUM of (n*(n*n + 1) / 2) if it's a valid square
Also the middle sub square:
x y
z t
*/
inline bool subAccept(int p, int i, int j) {
    return (y[p - 1][i - 1] + y[p - 1][i] + y[p][i - 1] + j == 34);
}


/*
These corners have to have the SUM of (n*(n*n + 1) / 2) if it's a valid square

x _ x _     _ x _ x
_ _ _ _     _ _ _ _
x _ x _     _ x _ x
_ _ _ _     _ _ _ _


_ _ _ _     _ _ _ _
x _ x _     _ x _ x
_ _ _ _     _ _ _ _
x _ x _     _ x _ x
*/
inline bool miniCorners(int p, int i, int j) {
    return (y[p - 2][i - 2] + y[p - 2][i] + y[p][i - 2] + j == 34);
}


//Check if the SUM of the diagonal is (n*(n*n + 1) / 2)
bool diag(int j) {
    int sum = 0;

    for (int k = 1; k < n; k++)
        sum += y[k][k];

    sum += j;

    return (sum == 34);
}


//Check if the SUM of the other diagonal(sorry I don't know what it is called in english) is (n*(n*n + 1) / 2)
bool revDiag(int j) {
    int sum = 0;

    for (int k = 1; k < n; k++)
        sum += y[k][n - k + 1];

    sum += j;

    return (sum == 34);
}

//Print to screen
void show() {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++)
            if (y[i][j] > 9)
                cout << " " << y[i][j];
            else
                cout << "  " << y[i][j];
        cout << endl;
    }
    cout << endl;

    if (dbug) {
        cin.get();
    }
}

//Call all the above functions to see if it's a Magic Square
inline bool magic(int p, int i, int j) {
    //3x3 Magic Sqaure
    //These are only patterns, only the colAccept(i, j) is called
    //This works fine
    if (n == 3) {
        if (p == 2 && i == 1 && j != 1 && j != 9 && y[1][2] != 1 && y[1][2] != 9)
            return false;

        if (p == 2 && i == 3 && j != 1 && j != 9 && y[1][2] != 1 && y[1][2] != 9 && (y[2][1] != 1 || y[2][1] != 9))
            return false;

        if (p == 3 && i == 2 && j != 1 && j != 9 && y[1][2] && y[1][2] && y[2][1] != 1 && y[2][1] != 9 && y[2][3] != 1 && y[2][3] != 9)
            return false;

        if (p == 2 && i == 2 && j != 5)
            return false;

        if (p == 3 && !colAccept(i, j))
            return false;

        return true;
    }
    //4x4 Magic Sqaure
    //All the functions are needed and some more patterns checking
    //3456 solutions in 17-20 seconds instead of 7000+ (around 7008 or 7080, can't remember and can't find it anywhere)
    else if (n == 4)
    {
        //              SubAccept#1
    //------------------------------------------
    if (p == 2 && i == 2 && !subAccept(p, i, j))
        return false;

    if (p == 2 && i == 4 && !subAccept(p, i, j))
        return false;

    if (p == 3 && i == 3 && !subAccept(p, i, j))
        return false;
    //------------------------------------------


    /*
    _ _ _ _
    x _ _ x
    x _ _ x
    _ _ _ _

    SUM(x) = (n*(n*n + 1) / 2)
    */
    if (p == 3 && i == 4)
        return ((y[2][1] + y[2][4] + y[3][1] + j) == 34);


    //              MiniCorners
    //--------------------------------------------
    if (p == 3 && i == 3 && !miniCorners(p, i, j))
        return false;

    if (p == 3 && i == 4 && !miniCorners(p, i, j))
        return false;

    if (p == 4 && i == 3 && !miniCorners(p, i, j))
        return false;

    if (p == 4 && i == 4 && !miniCorners(p, i, j))
        return false;
    //--------------------------------------------


    /*
    x _ _ _
    _ _ _ y
    _ _ _ y
    x _ _ _

    x + x = y + y
    */
    if (p == 4 && i == 1)
        return ((j + y[1][1]) == (y[2][4] + y[3][4]));

    /*
    _ _ _ x
    _ y _ _
    _ _ y _
    x _ _ _

    x + x = y + y
    */
    if (p == 4 && i == 1)
        return ((y[1][4] + j) == (y[2][2] + y[3][3]));

    /*
    x _ _ x
    _ _ _ _
    _ _ _ _
    _ y y _

    x + x = y + y
    */
    if (p == 4 && i == 3)
        return ((j + y[4][2]) == (y[1][4] + y[1][1]));

    /*
    _ x x _
    _ _ _ _
    _ _ _ _
    _ x x _

    SUM(x) = (n*(n*n + 1) / 2)
    */
    if (p == 4 && i == 3)
        return ((y[1][2] + y[1][3] + y[4][2] + j) == 34);


    //other diagonal
    if (p == 4 && i == 1 && !revDiag(j))
        return false;

    //If in last ROW check for COLUMN SUM = (n*(n*n + 1) / 2)
    if (p == 4 && !colAccept(i, j))
        return false;

    //diagonal
    if (p == 4 && i == 4 && !diag(j))
        return false;



    //              SubAccept#2
    //------------------------------------------
    if (p == 4 && i == 2 && !subAccept(p, i, j))
        return false;

    if (p == 4 && i == 4 && !subAccept(p, i, j))
        return false;
    //------------------------------------------

    return true;
    }
    else
    {
        cout << "You only have time for 3x3 and 4x4.\n";
        exit(0);
    }
}

//Basically permutations with backtracking
void perm(int i, int p) {
    for (int j = 1; j <= k; j++) {
        //If the number is not already in the square AND if the criteria for magic square is met
        if (!member[j] && magic(p, i, j)) {
            y[p][i] = j;            //Add to the magic square
            member[j] = true;       //It is now a member
            if (i < n) {            
                perm(i + 1, p);     //If FIRST ROW isn't full go again
            }
            else if (rowAccept(p)) {//If the SUM of the row is (n*(n*n + 1) / 2) {3x3: 15; 4x4: 34} continue
                if (p == n) {       //If we are in the last ROW
                    show();             //Show the solution
                    p = 1;                  //back to 1st ROW
                    nr++;           //Count the solutions
                }
                else {
                    perm(1, p + 1); //If it isn't the last ROW go to the next one
                }
            }

            member[j] = false;      //Number is no longer a member
        }                               //I'm not sure this is in the right place but I no longer know where to move it
    }
}

int main() {
    //Make sure there are no members
    for (int j = 1; j <= k; j++)
        member[j] = false;

    //3x3 or 4x4; else exit(0)
    cout << "Magic Square size 3x3 or 4x4 ?\n";
    cin >> n;
    k = n*n;

    //Stop at every solution ?
    cout << "Debug ?(0/1)\n";
    cin >> dbug;

    //Start timing and GO
    int start_s = clock();
    perm(1, 1);
    int stop_s = clock();

    cout << "Result: " << nr << endl;
    cout << "Time: " << (stop_s - start_s) / double(CLOCKS_PER_SEC) << " seconds\n";
}

我知道这很长,我的意大利面不容易理解,但我试着尽可能地评论。另外我很抱歉发布了整个内容,但我不知道哪个部分搞砸了解决方案的数量。

1 个答案:

答案 0 :(得分:0)

解决了!
事实证明,有两种类型的Magic Squares。
一方面它们被限制为数字n ^ 2(我的情况),另一方面它们可以包含任何数字,只要rowSUM,colSUM和diagSUM相等。
我的magic(...)函数中关于subAccept(),miniCorners()和其他中断条件的条件不适用于我的情况。我拿出来但现在代码速度较慢但结果是正确的。