第20講 一般種法による魔方陣ソフトの高速化
第3話 仮屋崎さんのエレガントな方法の解説

       void g(int n){
         char i,j;

         for(i=0;i<n;i++){
           for(j=0;j<n;j++){
             b[i][j]=-1;
           }
         }
         for(i=0;i<n;i++){
           b[i][i]=i;
         }
         cn=n;
         for(i=0;i<n;i++){
           if(b[i][n-1-i]==-1){
             b[i][n-1-i]=cn;
             cn++;
           }
         }
         
g1(n,0,1);
         
for(i=0;i<n;i++){
           for(j=0;j<n;j++){
             x[b[i][j]]=j;
             y[b[i][j]]=i;
           }
         }

       }
       void g1(int n,int y,int x){
         if(b[y][x]==-1){
           b[y][x]=cn;
           cn++;
         }
         if(x+1<n){
           g1(n,y,x+1);
         }
         else{
           t++;
           if(t<n){
             g2(n,t,t-1);
           }
         }
       }

       
void g2(int n,int y,int x){
         if(b[y][x]==-1){
           b[y][x]=cn;
           cn++;
         }
         if(y+1<n){
           g2(n,y+1,x);
         }
         else{
           if(t+1<n){
             g1(n,t,t+1);
           }
         }
       }


解説
         for(i=0;i<n;i++){
           for(j=0;j<n;j++){
             b[i][j]=-1;
           }
         }
         for(i=0;i<n;i++){
           b[i][i]=i;
         }
         cn=n;
         for(i=0;i<n;i++){
           if(b[i][n-1-i]==-1){
             b[i][n-1-i]=cn;
             cn++;
           }
         }

         
for(i=0;i<n;i++){
           for(j=0;j<n;j++){
             x[b[i][j]]=j;
             y[b[i][j]]=i;
           }
         }


ピンクは、対角線と逆対角線の番号付けですし、
薄緑は、番号から座標へ関連づけをしています。
薄緑の直前までが座標から番号への関連づけだったのを反転させているのです。
詳しいことは、第17講(再講義)第3話を参照にして下さい。

そして、今回の改訂のメインになるのは、g1とg2です。

0 1 2 3 4 5 6 7 8 9
0 0 -1 -1 -1 -1 -1 -1 -1 -1 10
1 -1 1 -1 -1 -1 -1 -1 -1 11 -1
2 -1 -1 2 -1 -1 -1 -1 12 -1 -1
3 -1 -1 -1 3 -1 -1 13 -1 -1 -1
4 -1 -1 -1 -1 4 14 -1 -1 -1 -1
5 -1 -1 -1 -1 15 5 -1 -1 -1 -1
6 -1 -1 -1 16 -1 84 6 -1 -1 -1
7 -1 -1 17 -1 -1 -1 -1 7 -1 -1
8 -1 18 -1 -1 -1 -1 -1 -1 8 -1
9 19 -1 -1 -1 -1 -1 -1 -1 -1 9

g1は行部分(表の)を、g2は列部分(表の

黄色

)をそれぞれ担当しています。動きを追ってみましょう。

まず、g1(n,0,1);で座標()すなわち

-1


へと飛びます。g1では最初に何をしているかと申しますと、
   if(b[y][x]==-1){
      b[y][x]=cn;
           cn++;
         }

b[0][1]が−1のままであるか調べています。
つまり、まだ本来の番号付けがなされていないか、調べています。
もし−1であれば、−1は仮の番号付けなので、本来の番号付けはなされていないことになります。
g1(n,0,1);ではじめて

-1


の世界(座標())に来たわけですから、当然仮の番号の−1が割り振られています。
そこでif文が実行され、b[y][x]=cn;によって19の次の番号20が割り振られ、

20


となり、
次の行のcn++;によってcnは21となります。
cnがすでに20であった理由は、
逆対角線の最後の処理
         for(i=0;i<n;i++){
           if(b[i][n-1-i]==-1){
             b[i][n-1-i]=cn;
             
cn++;
           }
         }

によって、座標()が

19

となっていて、さらにcn++;により20となっていたからです。
さて、このif文が実行された後、次のif文
   if(x+1<n){
      g1(n,y,x+1);
         }
         else{
           t++;
           if(t<n){
             g2(n,t,t-1);
           }
         }

に進みます。x+11+1の計算からです。ですから、x+1<n(nは10)を満たして、
g1(n,y,x+1);が実行され、座標()の世界

-1


へと飛びます。

-1


のときと同様に処理されて

-1


21

となります。x+1ですからx+1<nはまだ満たされていて、g1(n,y,x+1);が実行され

-1


の世界に飛び同様に処理されて、

-1


22

となります。以下同様にして、

0 1 2 3 4 5 6 7 8 9
0 0 21 22 23 24 25 26 27 -1 10
1 -1 1 -1 -1 -1 -1 -1 -1 11 -1
2 -1 -1 2 -1 -1 -1 -1 12 -1 -1
3 -1 -1 -1 3 -1 -1 13 -1 -1 -1
4 -1 -1 -1 -1 4 14 -1 -1 -1 -1
5 -1 -1 -1 -1 15 5 -1 -1 -1 -1
6 -1 -1 -1 16 -1 84 6 -1 -1 -1
7 -1 -1 17 -1 -1 -1 -1 7 -1 -1
8 -1 18 -1 -1 -1 -1 -1 -1 8 -1
9 19 -1 -1 -1 -1 -1 -1 -1 -1 9

となり、座標()の世界

-1


へ飛び、if文
   if(b[y][x]==-1){
      b[y][x]=cn;
           cn++;
         }

によって、

-1


28

となります。そして、
         if(x+1<n){
           g1(n,y,x+1);
         }
         else{
           t++;
           if(t<n){
             g2(n,t,t-1);
           }
         }

のとき、x+1は10(すなわちn)ではじめてelse文が実行されまして、
t++;によってtは1となり、if文
           if(t<n){
             g2(n,t,t-1);
           }

が実行され、座標()の世界

-1


へと飛翔します。

-1

は、if文
if(b[y][x]==-1){
   b[y][x]=cn;
   cn++;
           }

によって、

29

となります。
g1とg2の処理は、本質的に同じですが、
g1の横処理に対して、g2は縦処理ですので、下へ進みます。



         if(y+1<n){
           g2(n,y+1,x);
         }

g2(n,y+1,x);に注目すればそれは分かります。

-1


そして、どんどん仮番号−1は本当の番号に変わっていって、


0 1 2 3 4 5 6 7 8 9
0 0 21 22 23 24 25 26 27 28 10
1 29 1 -1 -1 -1 -1 -1 -1 11 -1
2 30 -1 2 -1 -1 -1 -1 12 -1 -1
3 31 -1 -1 3 -1 -1 13 -1 -1 -1
4 32 -1 -1 -1 4 14 -1 -1 -1 -1
5 33 -1 -1 -1 15 5 -1 -1 -1 -1
6 34 -1 -1 16 -1 84 6 -1 -1 -1
7 35 -1 17 -1 -1 -1 -1 7 -1 -1
8 36 18 -1 -1 -1 -1 -1 -1 8 -1
9 19 -1 -1 -1 -1 -1 -1 -1 -1 9

となります。この時点で、y+1は10(すなわちn)となり、
         if(y+1<n){
           g2(n,y+1,x);
         }
         else{
           if(t+1<n){
             g1(n,t,t+1);
           }
         }

のelse文が実行され、再びg1の世界すなわち横処理の世界へと進みます。
このようにして、横処理と縦処理が交互に進み、

0 1 2 3 4 5 6 7 8 9
0 0 20 21 22 23 24 25 26 27 10
1 28 1 36 37 38 39 40 41 11 42
2 29 43 2 50 51 52 53 12 54 55
3 30 44 56 3 62 63 13 64 65 66
4 31 45 57 67 4 14 72 73 74 75
5 32 46 58 68 15 5 80 81 82 83
6 33 47 59 16 76 84 6 88 89 90
7 34 48 17 69 77 85 91 7 94 95
8 35 18 60 70 78 86 92 96 8 98
9 19 49 61 71 79 87 93 97 99 9

が実現されるのです。

どうです。これもまさに見事です。すばらしいですね。

もう一つ、仮屋崎さんからメールで寄せられました。
今回紹介したものが、セル単位で進行するのに対して、
行または列単位で次に進む方法を次話で紹介しましょう。
実は、最初に寄せられたものは次話で紹介する行または列単位で進むものだったのですが、
私が熟読しないで早とちりしたものが、今回紹介したセル単位で進行するものだったのです。
早とちりの原因は、さっと読んで何も見ないでプログラミングするという私の学習法にあります。
家でさっと見た後、2日後に記憶のみによって、学校でVBAを使って再現したものが今回紹介したものです。
VBAを使った理由は、学校にはVC++がインストールされていないからです。


第20講(再講義)第2話へ  第20講(再講義)第4話へ


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