第16講 関数の再帰的呼び出しによる3次・4次魔方陣の作成
第5話 ソース解説その2

#pragma endregion
  private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
           DateTime^ hj=DateTime::Now;
           n=int::Parse(textBox1->Text);
           s=0;
           f(0);
           int i;
           array<String^>^ x=gcnew array<String^>(16);
           x[9]=L"魔";x[10]=L"方";x[11]=L"陣";x[12]=L"総";x[13]=L"数";x[14]=L":";x[15]=s.ToString();
           for(i=0;i<9;i++)x[i]=L"";
           dataGridView1->Rows->Add(x);
           DateTime^ ow=DateTime::Now;
           TimeSpan sa=ow->Subtract(*hj);
           x[10]=L"時";x[11]=L"間";x[12]=L"計";x[13]=L"則";x[14]=L":";x[15]=(sa.TotalSeconds).ToString();
           for(i=0;i<10;i++)x[i]=L"";
           dataGridView1->Rows->Add(x);
        }
        void f(char g){
           if(s==10)return;
           int i,j,k,h,x,y,xx,yy,wa;
           array<String^>^ w=gcnew array<String^>(16);
           x=g%n;
           y=g/n;
           for(i=0;i<n*n;i++){
             a[y][x]=i+1;
             h=1;
             if(g>0){
               for(j=0;j<g;j++){
                 xx=j%n;
                 yy=j/n;
                 if(a[y][x]==a[yy][xx]){
                   h=0;
                   break;
                 }
               }
             }
             if(h==1){
               if(x==n-1){
                 wa=0;
                 for(j=0;j<n;j++)wa+=a[y][j];
                 if(wa!=n*(n*n+1)/2)h=0;
               }
             }

             if(h==1){
               if(y==n-1){
                 wa=0;
                 for(j=0;j<n;j++)wa+=a[j][x];
                 if(wa!=n*(n*n+1)/2)h=0;
               }
             }

             if(h==1){
               if(x==n-1 && y==n-1){
                 wa=0;
                 for(j=0;j<n;j++)wa+=a[j][j];
                 if(wa!=n*(n*n+1)/2)h=0;
               }
             }

             if(h==1){
               if(x==0 && y==n-1){
                 wa=0;
                 for(j=0;j<n;j++)wa+=a[n-j-1][j];
                 if(wa!=n*(n*n+1)/2)h=0;
               }
             }

             if(h==1){
               if(g<n*n-1){
                 f(g+1);
               }
               else{
                 for(j=0;j<n;j++){
                   for(k=0;k<n;k++){
                      w[k]=(a[j][k]).ToString();
                   }
                   dataGridView1->Rows->Add(w);
                   }
                   for(j=0;j<16;j++)w[j]=L"";
                   dataGridView1->Rows->Add(w);
                   s++;
                   if(s==10)return;
                 }
               }
             }
          }
  };
}

では残っているピンク濃紺の部分の解説をしましょう。
まず、
             if(h==1){
               if(x==n-1){
                 wa=0;
                 for(j=0;j<n;j++)wa+=a[y][j];
                 if(wa!=n*(n*n+1)/2)h=0;
               }
             }

では何をしているかと言いますと

10 11
12 13 14 15

黄色の番号はg、赤色の番号はx、濃紺の番号はyに対応)
n=4の場合でトレースしていますので、x=n-1=3のとき、合計wan*(n*n+1)/2でないかどうか調べています。
wafor(j=0;j<n;j++)wa+=a[y][j];のfor文から横の合計であることが分かります。
n*(n*n+1)/2は4×(4×4+1)÷2=34です。これは4次魔方陣の横・縦・対角線の合計に一致します。
なぜなら方陣全体の合計は1+2+3+・・・+16=16×(16+1)÷2=4×4×(4×4+1)÷2ですから、
1行当たりの合計はそれを4で割った4×(4×4+1)÷2すなわち34です。
今、n=4の場合で説明しましたが、nのままでも同様な説明ができます。
n次方陣全体の合計はn×n×(n×n+1)÷2であり、1行当たりの合計はそれをnで割ったn×(n×n+1)÷2すなわちn*(n*n+1)/2です。
つまり
             if(h==1){
               if(x==n-1){
                 wa=0;
                 for(j=0;j<n;j++)wa+=a[y][j];
                 if(wa!=n*(n*n+1)/2)h=0;
               }
             }
でやっていることは、横の合計が34に等しくないならhを0にしなさいということなのです。
hが0になれば、もちろん
             if(h==1){
               if(g<n*n-1){
                 f(g+1);
               }
               else{
                 for(j=0;j<n;j++){
                   for(k=0;k<n;k++){
                      w[k]=(a[j][k]).ToString();
                   }
                   dataGridView1->Rows->Add(w);
                   }
                   for(j=0;j<16;j++)w[j]=L"";
                   dataGridView1->Rows->Add(w);
                   s++;
                   if(s==10)return;
                 }
               }
             }

の部分は実行されずi++によって、iは1つ進みます。

具体的なトレースは次話に回すことにして、次のピンク
             if(h==1){
               if(y==n-1){
                 wa=0;
                 for(j=0;j<n;j++)wa+=a[j][x];
                 if(wa!=n*(n*n+1)/2)h=0;
               }
             }

について説明しましょう。

10 11
12 13 14 15

黄色の番号はg、赤色の番号はx、濃紺の番号はyに対応)
y=n-1=3ですから、

12 13 14 15


の場合の相当します。
つまり列の一番下に来たら

                 wa=0;
                 for(j=0;j<n;j++)wa+=a[j][x];
                 if(wa!=n*(n*n+1)/2)h=0;

が実行されるわけです。for(j=0;j<n;j++)wa+=a[j][x];では縦合計を計算しています。
したがって、if(wa!=n*(n*n+1)/2)h=0;においては縦合計が34でないならhを0にせよの命令が実施されています。
hが0なら
             if(h==1){
               if(g<n*n-1){
                 f(g+1);
               }
               else{
                 for(j=0;j<n;j++){
                   for(k=0;k<n;k++){
                      w[k]=(a[j][k]).ToString();
                   }
                   dataGridView1->Rows->Add(w);
                   }
                   for(j=0;j<16;j++)w[j]=L"";
                   dataGridView1->Rows->Add(w);
                   s++;
                   if(s==10)return;
                 }
               }
             }

は実行されず、iはインクリメント(1つ進む)されます。

さて、それでは濃紺
             if(h==1){
               if(x==n-1 && y==n-1){
                 wa=0;
                 for(j=0;j<n;j++)wa+=a[j][j];
                 if(wa!=n*(n*n+1)/2)h=0;
               }
             }

では何がなされているのでしょうか。x=3かつy=3の場合とは、

10 11
12 13 14 15

黄色の番号はg、赤色の番号はx、濃紺の番号はyに対応)

15


対角線の一番下の場合に相当します。
ここに来たら、

                 wa=0;
                 for(j=0;j<n;j++)wa+=a[j][j];
                 if(wa!=n*(n*n+1)/2)h=0;

が実行されるわけです。今回のfor(j=0;j<n;j++)wa+=a[j][j];では何の合計をしているかと申しますと、

10 11
12 13 14 15


対角線を合計しています。
a[0][0]、 a[1][1]、 a[2][2]、 a[3][3]
がひだりの対角線ピンクに対応していることをご確認下さい。
ですから、濃紺においては対角線の合計が34でないなら、
hを0にしなさいという命令が行われているのです。










最後の
             if(h==1){
               if(x==0 && y==n-1){
                 wa=0;
                 for(j=0;j<n;j++)wa+=a[n-j-1][j];
                 if(wa!=n*(n*n+1)/2)h=0;
               }
             }

が難解です。ですが、皆さんはもう予想がついていますね。そうです。

10 11
12 13 14 15

黄色の番号はg、赤色の番号はx、濃紺の番号はyに対応)

12


逆対角線の一番下の場合に相当します。
なぜならx=0かつy=3ですから。逆対角線の一番下に来ると

                 wa=0;
                 for(j=0;j<n;j++)wa+=a[n-j-1][j];
                 if(wa!=n*(n*n+1)/2)h=0;

が実行されます。a[n-j-1][j]に皆さんは頭を悩ませたことでしょう。
いったいなんでしょうか。トレースしてみましょう。
n=4ですから
j=0のとき、a[4-j-1][j]=a[3][0]です。
j=1のとき、a[4-j-1][j]=a[2][1]です。
j=2のとき、a[4-j-1][j]=a[1][2]です。
j=3のとき、a[4-j-1][j]=a[0][3]です。
では、 a[3][0]、 a[2][1]、 a[1][2]、 a[0][3]とは何でしょうか。

10 11
12 13 14 15


そうです。逆対角線を下から上に上がっているのです。
もし、逆対角線を上から下に下がりたいならa[j][n-j-1]
とすればよろしいということは、お分かりでしょうか。
j=0のとき、a[j][4-j-1]=a[0][3]です。
j=1のとき、a[j][4-j-1]=a[1][2]です。
j=2のとき、a[j][4-j-1]=a[2][1]です。
j=3のとき、a[j][4-j-1]=a[3][0]です。
下から上に上がって行くにしろ、上から下に下がって行くにしろ
結局は逆対角線の合計を計算していることに変わりはありません。

結局でやっていることは逆対角線の合計が34でないならhを0にしなさいの命令なのです。




では、皆さん次話以降では具体的にトレースしてみましょう。



第11講第6話へ
 第12講第1話へ 第14講第10話へ 第15講第10話へ 第16講第4話へ 第16講第6話へ


VC++講義第1部へ
vb講義へ
VB講義基礎へ
初心者のための世界で一番わかりやすいVisual C++入門基礎講座
初心者のための世界で一番わかりやすいVisual Basic入門基礎講座