第29講 細胞構成法による魔方陣の作成の普遍化△

第5話 
素数次でない奇数次魔方陣のコード解説
コード再掲
#include<stdlib.h>
#include<math.h> //平方根を求める計算のために必要
       ・
       ・
       ・
#pragma endregion
   private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
           label2->Text=L"";
           DateTime^ hj=DateTime::Now;
           n=int::Parse(textBox1->Text);
           char h=1;
           if(n<3 || n>14 || n%2==0){ //とりあえず9で動作確認
             label2->Text=L"TextBoxには3以上11以下の奇数\r\nを入力し再度実行ボタンを\r\n押して下さい。";
             h=0;
           }
           
s=0;
           if(n%2==1){
             cn=0;
             h=sh();  
//素数判定 素数なら1が返ってくる
           }
           else{
             h=1; 
//後に偶数の場合でも使えるように入れてある
           }
           if(h==0 && n%2==1){
             kf1(0);
             array<String^>^ w=gcnew array<String^>(31);
             int i;
             w[25]=n.ToString();w[26]=L"次";w[27]=L"魔";w[28]=L"方";w[29]=L"陣";w[30]=s.ToString();
             for(i=0;i<25;i++)w[i]=L"";
             dataGridView1->Rows->Add(w);
             DateTime^ ow=DateTime::Now;
             TimeSpan sa=ow->Subtract(*hj);
             w[25]=L"時";w[26]=L"間";w[27]=L"計";w[28]=L"則";w[29]=L":";w[30]=(sa.TotalSeconds).ToString();
             for(i=0;i<25;i++)w[i]=L"";
             dataGridView1->Rows->Add(w);
           }

           /*
           if(h==1){
             s=0;
             if(n%2==1)g1(n);
             if(n%2==0)g2(n);
             syokika();
             cn=0;
             zhy();
             sbs(0);
             sbg(0);
             array<String^>^ w=gcnew array<String^>(15);
             int i;
             w[9]=(2*n).ToString();w[10]=L"次";w[11]=L"魔";w[12]=L"方";w[13]=L"陣";w[14]=s.ToString();
             for(i=0;i<8;i++)w[i]=L"";
             dataGridView1->Rows->Add(w);
             DateTime^ ow=DateTime::Now;
             TimeSpan sa=ow->Subtract(*hj);
             w[9]=L"時";w[10]=L"間";w[11]=L"計";w[12]=L"則";w[13]=L":";w[14]=(sa.TotalSeconds).ToString();
             for(i=0;i<9;i++)w[i]=L"";
             dataGridView1->Rows->Add(w);
           }
           */
         }
         char sh(){ //素数判定
           double k;
           k=sqrt((double)n);
           char i;
           for(i=3;i<=k;i+=2){
             if(n%i==0)return(0);
           }
           return(1);
         }
         void kf1(int g){ //素数でない奇数の種1作り
           if(s==10)return;
           if(g==0){
             a1[0][0]=n/2+1;
             kf1(g+1);
           }
           char i,j,h,k,kk,kkk,l;
           if(g>0){
             kk=rand()%n;
             for(i=1;i<n+1;i++){
               kkk=((kk+i-1)%n)+1;
               a1[0][g]=kkk;
               h=1;
               for(j=0;j<g;j++){
                 if(a1[0][g]==a1[0][j]){
                   h=0;
                   break;
                 }
               }
               if(h==1){
                 if(g+1<n){
                   kf1(g+1);
                 }
                 else{
                   for(j=1;j<n;j++){
                     for(k=0;k<n;k++){
                       l=(j+k)%n;
                       a1[j][l]=a1[0][k];
                     }
                   }
                   kf2(0);
                 }
               }
             }
           }
         }
         void kf2(int g){ //素数でない奇数の種2作り
           if(s==10)return;
           if(g==0){
             a2[0][n-1]=n/2+1;
             kf2(g+1);
           }
           char i,j,h,k,kk,kkk,l;
           if(g>0){
             kk=rand()%n;
             for(i=1;i<n+1;i++){
               kkk=((kk+i-1)%n)+1;
               a2[0][n-1-g]=kkk;
               h=1;
               for(j=0;j<g;j++){
                 if(a2[0][n-1-g]==a2[0][n-1-j]){
                   h=0;
                   break;
                 }
               }
               if(h==1){
                 if(g+1<n){
                 kf2(g+1);
               }
               else{
                 for(j=1;j<n;j++){
                   for(k=0;k<n;k++){
                     l=((n-1)*j+k)%n;
                     a2[j][l]=a2[0][k];
                   }
                 }
                 for(j=0;j<n;j++){
                   for(k=0;k<n;k++){
                     mah[j][k]=n*(a1[j][k]-1)+a2[j][k];
                   }
                 }
                 array<String^>^ w=gcnew array<String^>(30);
                 for(j=0;j<n;j++){
                   for(k=0;k<n;k++){
                     w[k]=(mah[j][k]).ToString();
                   }
                   dataGridView1->Rows->Add(w);
                 }
                 for(j=0;j<30;j++)w[j]=L"";
                 dataGridView1->Rows->Add(w);
                 s++;
                 if(s==10)return;
               }
             }
           }
         }
       }

               ・
               ・
               ・
解説
#include<math.h> //平方根を求める計算のために必要
は、素数判定関数sh()において、sqrt()を使うために入れてあります。
素数判定関数sh()の()内が空欄になっているのは、nはグローバル変数になっていて渡す必要がないからです。
もし、nがローカル変数の場合は値をsh(n)として引き渡す必要があります。
もちろんその場合は、char sh(int n)としなければなりません。
nが素数であるかどうかは、nの平方根以下の奇数で割っていけば判定できます。
if(h==0 && n%2==1)によって偶数の場合は除かれているからです。
偶数の可能性がある場合には、2で割って余りが出ることを確認しなければなりませんが。
例えば、25なら5以下の奇数3,5で割り切れないことを確認すればよいのです。
5の2乗は25ですから、それより大きい数では割りきれません。

           if(h==0 && n%2==1){
             kf1(0);
             array<String^>^ w=gcnew array<String^>(31);
             int i;
             w[25]=n.ToString();w[26]=L"次";w[27]=L"魔";w[28]=L"方";w[29]=L"陣";w[30]=s.ToString();
             for(i=0;i<25;i++)w[i]=L"";
             dataGridView1->Rows->Add(w);
             DateTime^ ow=DateTime::Now;
             TimeSpan sa=ow->Subtract(*hj);
             w[25]=L"時";w[26]=L"間";w[27]=L"計";w[28]=L"則";w[29]=L":";w[30]=(sa.TotalSeconds).ToString();
             for(i=0;i<25;i++)w[i]=L"";
             dataGridView1->Rows->Add(w);
           }

は列数が増えたための変更です。

         char sh(){ //素数判定
           double k;
           k=sqrt((double)n);
           char i;
           for(i=3;i<=k;i+=2){
             if(n%i==0)return(0);
           }
           return(1);
         }

についてはすでに説明してあります。
nの平方根以下で割って、割り切れると素数でなく、すべて余りが出る場合は素数です。
割り切れてしまった場合は、もうそれ以上ループ処理をする必要がないのでreturn(0);としています。
これもループを強制的にやめる方法のひとつです。
(double)nはsqrt()に入れることが出来る数は、double型に決まっているからです。
int型を強制的にdouble型に変更しています。

         void kf1(int g){ //素数でない奇数の種1作り
           if(s==10)return;
           if(g==0){
             a1[0][0]=n/2+1;
             kf1(g+1);
           }
           char i,j,h,k,kk,kkk,l;
           if(g>0){
             kk=rand()%n;
             for(i=1;i<n+1;i++){
               kkk=((kk+i-1)%n)+1;
               a1[0][g]=kkk;
               h=1;
               for(j=0;j<g;j++){
                 if(a1[0][g]==a1[0][j]){
                   h=0;
                   break;
                 }
               }
               if(h==1){
                 if(g+1<n){
                   kf1(g+1);
                 }
                 else{
                   for(j=1;j<n;j++){
                     for(k=0;k<n;k++){
                       l=(j+k)%n;
                       a1[j][l]=a1[0][k];
                     }
                   }
                   kf2(0);
                 }
               }
             }
           }
         }

のおいては、種の1番目の作成を行っています。
奇数次魔方陣種の制約によって、順列の最初は真ん中の数字と決まっていますので、
           if(g==0){
             a1[0][0]=n/2+1;
             kf1(g+1);
           }

となっています。kf1(g+1);を忘れると、セル番号0のみを埋めて関数は終わってしまいます。
kf1(g+1);によって、次の入れ子式人形の世界=セル番号1の世界に進みます。
セル番号1以降は、
           if(g>0){
             kk=rand()%n;
             for(i=1;i<n+1;i++){
               kkk=((kk+i-1)%n)+1;
               a1[0][g]=kkk;
1からnまでの数字をランダムに入れていきます。
kkk=((kk+i-1)%n)+1;はkkが何であっても、1からnの場合をすべて網羅します。
真ん中の数字は、セル番号1以降は入れられませんが、その可能性は後のif文で排除しています。
例えば、n=9でkk=5(kkはランダム発生した整数を9で割った余りなので9未満の整数です)の場合でトレースすると
i=1のとき、
kkk=((kk+
-1)%n)+1=((5+-1)%9)+1=(5%9)+1=5+1=6
i=2のとき、
kkk=((kk+
-1)%n)+1=((5+-1)%9)+1=(6%9)+1=6+1=7
i=3のとき、
kkk=((kk+
-1)%n)+1=((5+-1)%9)+1=(7%9)+1=7+1=8
i=4のとき、
kkk=((kk+
-1)%n)+1=((5+-1)%9)+1=(8%9)+1=8+1=9
i=5のとき、
kkk=((kk+-1)%n)+1=((5+
-1)%9)+1=(9%9)+1=0+1=1
i=6のとき、
kkk=((kk+-1)%n)+1=((5+
-1)%9)+1=(10%9)+1=1+1=2
i=7のとき、
kkk=((kk+-1)%n)+1=((5+
-1)%9)+1=(11%9)+1=2+1=3
i=8のとき、
kkk=((kk+-1)%n)+1=((5+
-1)%9)+1=(12%9)+1=3+1=4
i=9のとき、
kkk=((kk+-1)%n)+1=((5+
-1)%9)+1=(13%9)+1=4+1=5
どうです。見事に6,7,8,9,1,2,3,4,5すべてが網羅されていますね。
その他いかなる場合でトレースしても必ず漏れなくすべての整数が現れることを確認して下さい。
               h=1;
               for(j=0;j<g;j++){
                 if(a1[0][g]==a1[0][j]){
                   h=0;
                   break;
                 }
               }

は、前のセルと同じ数字が入っていないかチェックしています。
同じ数字が発見されるとhは0となり
               if(h==1){
                 if(g+1<n){
                   kf1(g+1);
                 }
                 else{
                   for(j=1;j<n;j++){
                     for(k=0;k<n;k++){
                       l=(j+k)%n;
                       a1[j][l]=a1[0][k];
                     }
                   }
                   kf2(0);
                 }
               }

は実行されず、iは次へとループされます。
このif文において真ん中の数字も排除されていることがわかります。
なぜなら、

となった時点でチェックされiはインクリメントされ

となるからです。
一番最初にセル番号0に5が入れられているので、
前のセルとの重複チェックにおいて自動的に真ん中の数字の可能性が排除されるのです。
                 if(g+1<n){
                   kf1(g+1);
                 }

は、セル番号の最後に来るまで実行されます。
つまり、入れ子式の人形の一番内側の人形に達するまで跳躍が続けられます。

最後のセルに達すると、もっとも奥にある本当の自分の姿を見いだし、自分探しの旅は終わりを告げ、
                 else{
                   for(j=1;j<n;j++){
                     for(k=0;k<n;k++){
                       l=(j+k)%n;
                       a1[j][l]=a1[0][k];
                     }
                   }
                   kf2(0);
                 }

の命令が遂行されます。
さて、
                   for(j=1;j<n;j++){
                     for(k=0;k<n;k++){
                       l=(j+k)%n;
                       a1[j][l]=a1[0][k];
                     }
                   }

では何をしているのでしょうか。

   0 1  2  3  4  5  6  7  8 
5 3 6 1 4 9 2 7 8
8 5 3 6 1 4 9 2 7
7 8 5 3 6 1 4 9 2
2 7 8 5 3 6 1 4 9
9 2 7 8 5 3 6 1 4
4 9 2 7 8 5 3 6 1
1 4 9 2 7 8 5 3 6
6 1 4 9 2 7 8 5 3
3 6 1 4 9 2 7 8 5

実は、1行目の順列を1ずらして1個目の種を完成させています。
トレースをして確認してみましょう。
  
                 for(j=1;j<n;j++){
                     for(k=0;k<n;k++){
                       l=(j+k)%n;
                       a1[j][l]=a1[0][k];
                     }
                   }

Ⅰ j=1の場合
 ⅰ 
k=0のとき、
    
=()%n=()%9=1%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には5がはいります。
 ⅱ 
k=1のとき、
    
=()%n=()%9=1%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には3がはいります。
 ⅲ k=2のとき、
    
=()%n=()%9=3%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には6がはいります。
 ⅳ k=3のとき、
    
=()%n=()%9=4%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には1がはいります。
 ⅴ k=4のとき、
    
=()%n=()%9=5%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には4がはいります。
 ⅵ k=5のとき、
    
=()%n=()%9=6%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には9がはいります。
 ⅶ k=6のとき、
    
=()%n=()%9=7%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には2がはいります。
 ⅷ k=7のとき、
    
=()%n=()%9=8%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には7がはいります。
 ⅸ k=8のとき、
    
=()%n=()%9=9%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には8がはいります。

   0 1  2  3  4  5  6  7  8 
5 3 6 1 4 9 2 7 8
8 5 3 6 1 4 9 2 7
7 8 5 3 6 1 4 9 2
2 7 8 5 3 6 1 4 9
9 2 7 8 5 3 6 1 4
4 9 2 7 8 5 3 6 1
1 4 9 2 7 8 5 3 6
6 1 4 9 2 7 8 5 3
3 6 1 4 9 2 7 8 5

Ⅱ j=2の場合
 ⅰ 
k=0のとき、
    
=()%n=()%9=2%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には5がはいります。
 ⅱ 
k=1のとき、
    
=()%n=()%9=3%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には3がはいります。
 ⅲ k=2のとき、
    
=()%n=()%9=4%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には6がはいります。
 ⅳ k=3のとき、
    
=()%n=()%9=5%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には1がはいります。
 ⅴ k=4のとき、
    
=()%n=()%9=6%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には4がはいります。
 ⅵ k=5のとき、
    
=()%n=()%9=7%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には9がはいります。
 ⅶ k=6のとき、
    
=()%n=()%9=8%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には2がはいります。
 ⅷ k=7のとき、
    
=()%n=()%9=9%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には7がはいります。
 ⅸ k=8のとき、
    
=()%n=()%9=10%9=より
    a1[
][]=a1[0][]は、a1[][]=a1[0][]となり、
    a1[
][]には8がはいります。

以下トレースはご自分で確認して下さい。

最後の
         void kf2(int g){ //素数でない奇数の種2作り
           if(s==10)return;
           if(g==0){
             a2[0][n-1]=n/2+1;
             kf2(g+1);
           }
           char i,j,h,k,kk,kkk,l;
           if(g>0){
             kk=rand()%n;
             for(i=1;i<n+1;i++){
               kkk=((kk+i-1)%n)+1;
               a2[0][n-1-g]=kkk;
               h=1;
               for(j=0;j<g;j++){
                 if(a2[0][n-1-g]==a2[0][n-1-j]){
                   h=0;
                   break;
                 }
               }
               if(h==1){
                 if(g+1<n){
                 kf2(g+1);
               }
               else{
                 for(j=1;j<n;j++){
                   for(k=0;k<n;k++){
                     l=((n-1)*j+k)%n;
                     a2[j][l]=a2[0][k];
                   }
                 }
                 for(j=0;j<n;j++){
                   for(k=0;k<n;k++){
                     mah[j][k]=n*(a1[j][k]-1)+a2[j][k];
                   }
                 }
                 array<String^>^ w=gcnew array<String^>(30);
                 for(j=0;j<n;j++){
                   for(k=0;k<n;k++){
                     w[k]=(mah[j][k]).ToString();
                   }
                   dataGridView1->Rows->Add(w);
                 }
                 for(j=0;j<30;j++)w[j]=L"";
                 dataGridView1->Rows->Add(w);
                 s++;
                 if(s==10)return;
               }
             }
           }
         }
       }

については、種1作りとほとんど同じです。違う点は3点あります。
まず、1点目は、a2[0][n-1-g]=kkk;をごらんになればお分かりのようにセルを逆順で埋めていることです。
つまり最初にセル番号n-1番目に数字を入れ、次にn-2番目という風に入れています。
これは、最後のセルが真ん中の数字に決まっているので、そこと重複しないようにするためです。
もし、正順で入れていくとなるといちいち最後のセルとの重複チェックをしなければなりません。
つまり、
               h=1;
               
if(a2[0][g]==a2[0][n-1])h=0;
               if(h==1){
                 for(j=0;j<g;j++){
                   if(a2[0][g]==a2[0][j]){
                     h=0;
                     break;
                   }
                 }
               }

としなければならないことになって余計な手間がかかります。
2点目は、
                 for(j=1;j<n;j++){
                   for(k=0;k<n;k++){
                     l=((n-1)*j+k)%n;
                     a2[j][l]=a2[0][k];
                   }
                 }

におけるずらし方の違いです。n-1ずらし(n=9なら8ずらし)は逆1ずらしに相当することを下表からご確認ください。

6 2 9 1 8 3 4 7 5
2 9 1 8 3 4 7 5 6
9 1 8 3 4 7 5 6 2
1 8 3 4 7 5 6 2 9
8 3 4 7 5 6 2 9 1
3 4 7 5 6 2 9 1 8
4 7 5 6 2 9 1 8 3
7 5 6 2 9 1 8 3 4
5 6 2 9 1 8 3 4 7

トレースは種1の場合を参考にご自分でなさってください。
3点目は、
                 for(j=0;j<n;j++){
                   for(k=0;k<n;k++){
                     mah[j][k]=n*(a1[j][k]-1)+a2[j][k];
                   }
                 }
                 array<String^>^ w=gcnew array<String^>(30);
                 for(j=0;j<n;j++){
                   for(k=0;k<n;k++){
                     w[k]=(mah[j][k]).ToString();
                   }
                   dataGridView1->Rows->Add(w);
                 }
                 for(j=0;j<30;j++)w[j]=L"";
                 dataGridView1->Rows->Add(w);
                 s++;
                 if(s==10)return;

です。これは種の合体による魔方陣の作成と表示を行っています。



第4話へ
第6話へ

戻る

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