第17講 関数の再帰的呼び出しによる3次・4次魔方陣ソフトの改良
第1話 プログラムの改良

今からプログラムの改良に入りますが、
実は前回作ったプログラムは3次・4次魔方陣に限定されたプログラムではありません。
理論的には100次魔方陣でも作ることのできるプログラムです。
もちろん冒頭の
#pragma once
int n;
int a[10][10];
int s;

#pragma once
int n;
int a[100][100];
int s;
と変更しなければなりませんが。
理論的には int a[n][n];としておけば、3
以上でn以下の整数なら何次の魔方陣でも作ることのできる普遍的なプログラムです。
だから、デザインの方も
としておいてもてよいのです。
15以下としたのは、DataGridViewがまだ15列しかないからです。
だから、5を入力して忍耐強く待ち続ければいつかは魔方陣は出てきます。
もちろん、
void f(char g){
  if(s==10)return;
  int i,j,k,h,x,y,xx,yy,wa;
          .
          .
          .
        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(s==10)return;入れておくことが前提です。このプログラムでは21億個の魔方陣を作成させるには、1000年かけても終わらないからです。
DataGridViewは残念ながらリアルタイムに表示させることができず、すべての計算が終わってからしか結果が表示されないのです。
ただし、if(s==1)return;としても、私のパソコンでは6時間待っても結果は表示されませんでした。
もし6を入力したなら100日待ったも出てこないかもしれません。

ところが、これからちょっととした改良を加えるだけで、
5次魔方陣なら1個出てくるまでの時間は、0.3744秒(私の使用パソコンで)まで縮めることができます。
6時間待っても一つも表示されなかったので、
仮に1個出てくるまでの時間が6時間であるとして計算しもて、6万倍以上の作成速度の差があることになります。
4次の場合は、7040個のすべてを作り出すスピードは17倍程度ですから、
次数が一つ増えるだけで大きく作成速度が違ってくることが分かります。
したがって、6次以降については、作成速度が1000万倍以上は軽く越えるでしょう。

さて、その小改良とは枠番後gの付け方を変えるだけです。

10 11
12 13
14 15


4
10 11
12 13 14
15 16 17 18
19 20 21
4 22 23 24

赤の番号はx、濃紺の番号はy、ピンクの番号はgに対応)

つまり、対角線→逆対角線→その他の順に変更するだけで、5次魔方陣において3万倍以上も作成速度が大きくなるのです。
さて、問題は番号の付け替えをどのようにするかです。
前回においては、x=g%n; y=g/n;と座標をいちいち計算させていましたが、これは無駄です。
毎回座標を計算し直しているわけですから。
ですから、座標作成関数g1とg2を用意しておいて、まず最初に座標を計算させておきたいものです。
関数を二つ用意する理由は、偶数次と奇数次では作成ルールが違うからです。
上の4次と5次を比較して頂ければそれは理解して頂けると思います。
もし、番号の付け方が

10 11
12 13 14 15

赤の番号はx、濃紺の番号はy、ピンクの番号はgに対応)
なら、用意する関数は一つでよく、
#pragma once
int n;
int a[10][10];
int x[100],y[100];
int s;
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
         DateTime^ hj=DateTime::Now;
         n=int::Parse(textBox1->Text);
         s=0;
         g(n);
         f(0);
           ・
           ・
           ・
      }
      void g(char n){
         int i;
         for(i=0;i<n*n;i++){
           x[i]=i%n;
           y[i]=i/n;
         }
      }

というプログラム例が考えられます。

10 11
12 13
14 15


4
10 11
12 13 14
15 16 17 18
19 20 21
4 22 23 24

赤の番号はx、濃紺の番号はy、ピンクの番号はgに対応)
のように番号付けを行うにはどうしたらよいでしょうか。

3次・4次に限定するなら、
void g1(char n){
  x[0]=0;
  x[1]=1;
  x[2]=2;
  x[3]=2;
  x[4]=0;
  x[5]=1;
  x[6]=0;
  x[7]=2;
  x[8]=1;
  y[0]=0;
  y[1]=1;
  y[2]=2;
  y[3]=0;
  y[4]=2;
  y[5]=0;
  y[6]=1;
  y[7]=1;
  y[8]=2;
}

void g2(char n){
  x[0]=0;
  x[1]=1;
  x[2]=2;
  x[3]=3;
  x[4]=3;
  x[5]=2;
  x[6]=1;
  x[7]=0;
  x[8]=1;
  x[9]=2;
  x[10]=0;
  x[11]=3;
  x[12]=0;
  x[13]=3;
  x[14]=1;
  x[15]=2;
  y[0]=0;
  y[1]=1;
  y[2]=2;
  y[3]=3;
  y[4]=0;
  y[5]=1;
  y[6]=2;
  y[7]=3;
  y[8]=0;
  y[9]=0;
  y[10]=1;
  y[11]=1;
  y[12]=2;
  y[13]=2;
  y[14]=3;
  y[15]=3;
}
でもいいわけですが、これは芸がありません。
プログラムにおいて大事なのは、如何にして一般化するかです。
今回の改良で実際に扱える(時間的に可能なのは)5次までです。
6次以降だと軽く1000万倍以上は速くなっていますが、
それでも6次以降だと太刀打ちできないのです。
ですが、将来のおいては、26次あたりまで扱えるようになります。
講義が進んでいけば、26次でもコンピュータは1秒に数千個の勢いで魔方陣を作れるようになります。
ですから、将来に向けて座標番号付けは是非とも普遍的(一般的)なものにしてほしいのです。
前に示した
      void g(char n){
         int i;
         for(i=0;i<n*n;i++){
           x[i]=i%n;
           y[i]=i/n;
         }
      }

は普遍的なプログラムです。nには自由に整数を入力することができるからです。

10 11
12 13
14 15


4
10 11
12 13 14
15 16 17 18
19 20 21
4 22 23 24

の場合も普遍的なプログラムを考えてほしいわけです。
超難解ですよ。
頭が爆発しそうになるほど。

今回は、30分考えて分からなければ第2話を開きましょう。
今回は超ド級の難問だからです。


第16講第7話へ  第17講第2話へ


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