C言語で3×3マジックスクエアパズルを解く方法 Python

⚡ スマートサマリー

魔方陣パズルは、n×nのマス目の中に連続する数字を配置し、すべての行、列、および主対角線の合計が同じになるようにするパズルです。この合計は「魔方陣定数」と呼ばれ、娯楽としての数学やアルゴリズム的思考の古典的な練習問題となっています。

  • 🔢 魔法の定数式: n次の通常の魔方陣の場合、魔方陣の合計はn(n²+1)/2に等しくなり、3次の場合は15、7次の場合は175になります。
  • 🧩 シャムメソッド: 奇数次の魔方陣は、最上段の中央に1を配置し、折り返しと衝突のルールを処理しながら右上に移動することで生成されます。
  • 📐 正方形バリエーション: 魔方陣は、通常、準魔方陣、単純魔方陣、完全魔方陣に分類され、それぞれの分類は、どの合計が魔方陣定数と一致しなければならないかによって定義されます。
  • 動作する実装例: 同じ C++ (NAIST) と Python プログラムは、O(n²)の補助空間を使用して、任意の奇数次の平方数をO(n²)の時間で構築します。
  • 🧪 ステップバイステップのデモ: 3×3の詳細な手順説明により、9つの配置それぞれがどのように行、列、対角線のルールを満たしているかが示されます。

魔方陣とは何ですか?

魔方陣とは、数字が特殊な配置で並べられた正方行列のことです。各行、各列、そして両方の主対角線の合計が常に一定になるように数字が配置されています。魔方陣は、娯楽としての数学で用いられるシンプルな論理パズルです。

魔方陣の例:

マジックスクエア

上の図は、3次の魔方陣を示しています。すべての対角線、行、列の合計は15になります。次のセクションでは、この一定の合計値がどのように生成されるかを説明します。

魔法陣の仕組み

n次魔方陣とは、n²個の正の整数を含むn×n行列のことである。行数または列数を行列の次数と呼ぶ。

典型的な魔方陣パズルは奇数次で、1からn²までの整数を使用します。すべての行、列、対角線の合計が同じ値になる必要があるため、その値を魔方陣の和または魔方陣定数と呼びます。この定数はnのみに依存します。n次の魔方陣の和の公式は次のとおりです。

魔方陣の作品

3次の魔方陣を考えてみましょう。その魔方陣の和は次のようになります。

魔方陣の作品

魔方陣の作品

この公式は算術的な仕組みを説明するものだが、このパズルには長い文化的歴史があり、それがこの印象的な名前の由来となっている。

なぜそれらは魔法と呼ばれるのか?

古代の数学者たちは、興味深い数の組み合わせに魅了されており、魔方陣もその一つだった。最も古い証拠は、紀元前190年頃の中国に遡る。

研究によると、古代の日本、インド、アラビアには魔方陣パズルが存在していた証拠がある。伝説ではこれらの配置が魔法の世界と結びつけられ、その名前が定着した。民間伝承を超えて、数学者たちは、それぞれの方陣を区別するための形式的なカテゴリーも定義している。

魔方陣の種類

数学には魔方陣のいくつかのバリエーションが存在する。

  • 通常の魔方陣: 最初の n² 個の自然数を含みます。
  • 準魔方陣: 行と列の合計だけが、魔法の定数に一致する。
  • シンプルな魔方陣: 行、列、および両方の主対角線の合計は、魔法の定数になります。
  • 最も完璧な魔方陣: 通常の魔方陣に2つの追加特性があります。この行列の2×2の小正方形の合計は2(n²+1)になり、n/2個のセルを隔てた任意の2つの数字の合計はn²+1になります。

追加の特性に基づいて、さらに多くの分類が存在します。このチュートリアルで「魔方陣」という用語が特に断りなく使用されている場合は、奇数次の通常の単純な魔方陣を指します。

魔方陣を生成するアルゴリズム

奇数次の魔方陣を生成する古典的なアルゴリズムであるシャム方式は以下のとおりです。

  • 最初の数値(1)は位置(n/2, n-1)に格納されます。ここで、最初の座標は行インデックス、2番目の座標は列インデックスです。後の手順では、この位置を(x, y)と呼びます。
  • 次の数値は(x-1, y+1)に配置されます。その位置が無効な場合は、次のルールを適用します。
    1. 行インデックスが-1の場合は、n-1に折り返します。列インデックスがnの場合は、0に折り返します。
    2. 計算結果の位置に既に数値が含まれている場合は、行を1増やし、列を2減らします。
    3. 行が-1で列がnの場合、新しい位置は(0, n-2)になります。

注意: このアルゴリズムは、奇数次の有効な魔方陣のみを生成します。結果として得られるのは、最初の n² 個の自然数を含む通常の魔方陣です。同じ n に対して、有効な解が複数存在する場合があります。

1から9までの数字を使った3次の簡単な例を通して、ルールがより明確になる。

3×3の正方形での仕組み

適用 アルゴリズム 上記の手順は以下のとおりです。

ステップ1) 最初の数(1)は(3/2, 3-1)または(1, 2)に配置されます。以降のステップでは、x = 1、y = 2と設定します。

魔方陣生成アルゴリズム

ステップ2) 残りの数字の位置は、以下のように計算されます。

番号 2 の位置:

次の数値は (x-1, y+1) または (0, 3) に入るはずですが、これは有効な位置ではありません。規則 (a) により、列は 0 に折り返され、(0, 0) になります。x = 0、y = 0 とします。

魔方陣生成アルゴリズム

番号 3 の位置:

3 番は (x-1, y+1) または (-1, 1) にあるはずですが、これは有効な位置ではありません。ルール (a) により、行は n-1 (つまり 2) に折り返されます。したがって、3 番は (2, 1) になります。x = 2、y = 1 とします。

魔方陣生成アルゴリズム

番号 4 の位置:

4番目の位置は(x-1, y+1)または(1, 2)であるべきですが、これは有効ですが既に1が含まれています。ルール(b)により、新しい位置は(1+1, 2-2)または(2, 0)です。x = 2、y = 0とします。

魔方陣生成アルゴリズム

番号 5 の位置:

5番は(x-1, y+1)または(1, 1)にあるべきで、これは有効な空き位置です。x = 1、y = 1と設定します。

魔方陣生成アルゴリズム

番号 6 の位置:

6番は(x-1, y+1)または(0, 2)にあるべきで、これは有効な空き位置です。x = 0、y = 2と設定します。

魔方陣生成アルゴリズム

番号 7 の位置:

7番は(x-1, y+1)または(-1, 3)にあるはずですが、これは無効です。規則(c)により、新しい位置は(0, n-2)または(0, 1)になります。x = 0、y = 1とします。

魔方陣生成アルゴリズム

番号 8 の位置:

8 番は (x-1, y+1) または (-1, 2) にあるはずですが、これは無効です。ルール (a) により、行は 2 に折り返され、(2, 2) になります。x = 2、y = 2 とします。

魔方陣生成アルゴリズム

番号 9 の位置:

9番は(x-1, y+1)または(1, 3)にあるべきですが、これは無効です。ルール(a)により、列は0に折り返され、(1, 0)になります。

魔方陣生成アルゴリズム

セルがすべて埋められると、同じロジックがそのまま擬似コードに変換される。

魔方陣の擬似コード

Begin
    Declare an array of size n*n
    Initialize the array to 0
    Set row = n/2
    Set column = n-1
    For all number i: from 1 to n*n
        If the row = -1 and column = n
            row = 0
            column = n-2
        Else
            If row = -1
                row = n-1
            If column = n
                column = 0
        If the position already contains a number
            decrement column by 2
            increment row by 1
            continue until the position is not 0
        Else
            put the number i into the calculated position
            increment i
        Increment column value
        Decrement row value
End

擬似コードはコンパイル言語とインタプリタ言語に直接対応しており、次に示すとおりである。 C++ (NAIST) と Python.

C++ Code マジックスクエア用

入力:

/*
A C/C++ program for generating odd order magic squares
*/
#include <bits/stdc++.h>
using namespace std;

void GenerateMagicSquare(int n)
{
    int magic[n][n];
    //initializing the array
    for(int i=0; i<n; i++)
        for(int j=0; j<n; j++)
            magic[i][j] = 0;
    //setting row and column value
    int i = n / 2;
    int j = n - 1;
    for (int k = 1; k <= n * n;)
    {
        //checking condition (c)
        if (i == -1 && j == n)
        {
            j = n - 2;
            i = 0;
        }
        else
        {
            //checking condition (a)
            if (j == n)
                j = 0;
            if (i < 0)
                i = n - 1;
        }
        //checking condition (b)
        if (magic[i][j])
        {
            j -= 2;
            i++;
            continue;
        }
        else
        {
            //placing the number into the array
            magic[i][j] = k;
            k++;
        }
        //for the next number setting (i-1, j+1)
        j++;
        i--;
    }
    //printing the matrix
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
            cout << magic[i][j] << "  ";
        cout << endl;
    }
}
int main()
{
    //This code works for only odd numbers
    int n = 7;
    cout<<"The magic sum is " << n*(n*n+1)/2 <<endl;
    GenerateMagicSquare(n);
    return 0;
}

例の出力:

The magic sum is 175

20  12  4  45  37  29  28
11  3  44  36  35  27  19
2  43  42  34  26  18  10
49  41  33  25  17  9  1
40  32  24  16  8  7  48
31  23  15  14  6  47  39
22  21  13  5  46  38  30

その Python 以下のバージョンでは、行と列のルールは同一です。

Python Code マジックスクエア用

def GenerateMagicSquare(n):
    #initializing the array
    magic = [[0 for x in range(n)]
                for y in range(n)]
    #setting row and column value
    i = n // 2
    j = n - 1
    k = 1
    while k <= (n * n):
        #checking condition (c)
        if i == -1 and j == n:
            j = n - 2
            i = 0
        else:
            #checking condition (a)
            if j == n:
                j = 0
            if i < 0:
                i = n - 1
        #checking conditon (b)
        if magic[i][j]:
            j = j - 2
            i = i + 1
            continue
        else:
            #placing the number into the array
            magic[i][j] = k
            k = k + 1
        #for the next number setting (i-1, j+1)
        j = j + 1
        i = i - 1
    #printing the matrix
    for i in range(0, n):
        for j in range(0, n):
            print('%2d ' % (magic[i][j]),end='')
            if j == n - 1:
                print()
#This code works for only odd numbers
n = 7
print("The magic sum is ",n * (n * n + 1) // 2, "\n")
GenerateMagicSquare(n)

例の出力:

The magic sum is  175

20 12  4 45 37 29 28
11  3 44 36 35 27 19
 2 43 42 34 26 18 10
49 41 33 25 17  9  1
40 32 24 16  8  7 48
31 23 15 14  6 47 39
22 21 13  5 46 38 30

どちらの実装も動作は全く同じなので、コスト比較は容易です。

複雑さの分析

  • スペースの複雑さ: 魔方陣はn×nの配列に格納されるため、空間計算量はO(n²)となる。
  • 時間計算量: このジェネレーターは2つのネストされたループを使用します。外側のループはn回実行され、内側のループもn回実行されるため、全体の時間計算量はO(n²)となります。

よくあるご質問

1から9までの数字を含む通常の3×3魔方陣の場合、魔方陣定数は15です。すべての行、列、および主対角線の合計は15でなければなりません。これは、nが3の場合の公式n(n²+1)/2から導き出されます。

いいえ。このチュートリアルで紹介するシャム方式は、奇数次の魔方陣にのみ適用されます。偶数次の魔方陣には、二重偶数(nが4で割り切れる)や単一偶数(nが4k+2)といった、異なるルールを用いた別のアルゴリズムが必要です。

ジェネレーターはn×n行列を埋めるため、時間計算量と空間計算量はともにO(n²)です。各セルは一定回数訪問され、記憶領域はちょうどn²個の整数です。このため、このアルゴリズムは一般的なレクリエーション用サイズのゲームにおいて効率的です。

遺伝的アルゴリズム、シミュレーテッドアニーリング、制約充足ソルバーなどのAI技術は、偶数次数、部分的な魔方陣、素数のみの魔方陣や幾何魔方​​陣といった追加制約を持つ変種など、閉形式の手法が適用できない場合でも、有効な魔方陣を探索することができる。

魔方陣は、組み合わせ最適化、強化学習エージェント、ニューラル探索におけるベンチマーク問題です。研究者たちは、解の検証は容易であるものの、その数を数えることは未解決の数学的問題であるため、構造化された離散空間上でヒューリスティクス、メタヒューリスティクス、AIプランナーをテストするために魔方陣を使用します。