第20講 一般種法による魔方陣ソフトの高速化 
第2話 仮屋崎さんのエレガントな方法
混沌として方針が全然見えない、出口のない迷宮のように見えた問題も、
次のように色塗りをすると急に展望が開けてきます。
| 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 | 
それでは解答例として仮屋崎さんのものを再び採用させて頂きます。
ただし、初心者用に少し変えてあります。
仮屋崎さんはグローバル変数を使わないもう少し高度なものです。
わかりやすさを優先して、グローバル変数を使いました。
#pragma once
#include<stdlib.h>
int n;
int a1[20][20],a2[20][20],p[20][20],cn1[20],cn2[20],b[20][20],cn;
int x[400],y[400];
int s;
int sk;
int t=0;
namespace 一般種法仮屋崎版 {
         ・
         ・
         ・
#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);
          int h=1;
          if(n<3 || n>=12){
            label2->Text=L"TextBoxには3以上11以下の整数\r\nを入力し再度実行ボタンを\r\n押して下さい。";
            h=0;
          }
          if(h==1){
            s=0;
            t=0;
            g(n);
            int i,j;
            array<String^>^ w=gcnew array<String^>(16);
            for(i=0;i<2*n;i++){
              a1[y[i]][x[i]]=0;
            }
            for(i=0;i<n*n;i++){
              a1[y[i]][x[i]]=i;         //番号付け確認ために入れてある。
            }                    //動作が確認でき次第ここは削る。
            for(i=0;i<n;i++){
              for(j=0;j<n;j++){
                w[j]=(a1[i][j]).ToString();
              }
              dataGridView1->Rows->Add(w);
           }
           for(i=0;i<14;i++)w[i]=L"";
           dataGridView1->Rows->Add(w);
           /*syokika();
             ・
              ・           とりあえず番号付けを確認するためにここは注釈文にして
              ・           作動を止めてある。  
              ・
           */
         }
       }
          ・
          ・
          ・
       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);
           }
         }
       }
         ・
         ・
         ・
これは、私のもの(旧講義)とは比べものにならないほど簡単になっています。
しかし、それでも初心者の方にはかなり難解ですので、皆さんが理解できるように
次話で詳しく解説していきます。
ここではポイントだけ述べておきましょう。
まず、第17講再講義第2話のときと同様に、
番号から座標へを
一回逆にして
座標から番号へ
を考え、再びそれを逆転させて、
番号から座標へ
と考えている点は同じです。
| 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 | 
さらに、仮屋崎さんは上の表をよく観察して、
処理が交互に行われている点に注目しました。
横→縦→横→縦→・・・
そして、それぞれの処理を関数の再帰的呼び出しという方法で難題を解決したのです。
(さらに、横→縦→横→縦→・・・に注目して、
再帰的呼び出しを使わずシンプルにプログラムする方法が、
読者の汪さんから提案されました。
       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++){
             for(j=i+1;j<n;j++){
               if(b[i][j]==-1){
                 b[i][j]=cn;
                 cn++;
               }
             }
             for(j=i+1;j<n;j++){
               if(b[j][i]==-1){
                 b[j][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;
             }
          }
       }
すばらしい方法ですね。
汪さんも仮屋崎さんと同様な優れたプログラミングセンスをお持ちですね。)
これなら、私の方法とは違い階差数列の知識を必要としません。
今回の方法もまさに目の覚める方法と言えます。
では次話以降で詳しく解説していきますので乞うご期待!
尚、
          if(n<3 || n>=12){
            label2->Text=L"TextBoxには3以上11以下の整数\r\nを入力し再度実行ボタンを\r\n押して下さい。";
            h=0;
          }
          if(h==1){
            s=0;
            t=0;
            g(n);
                ・
                ・
                ・
           /*syokika();
             ・
              ・           とりあえず番号付けを確認するためにここは注釈文にして
              ・           作動を止めてある。  
              ・
           */
         }
については、どこかの講で解説したのですが、
その講が見つかりませんので再度説明しておきましょう。
これは、今まで入れてきませんでしたが、
TextBoxに値が正しく入力されていないとエラーをするので、
正しく入力されているか点検するものです。
正しく、入力されていないとlabel2に「TextBoxには3以上11以下の整数\r\nを入力し再度実行ボタンを\r\n押して下さい。」
と表示させ、次の入力と実行ボタンのクリックを待つようにしたわけです。
VC++講義第1部へ
vb講義へ
VB講義基礎へ
初心者のための世界で一番わかりやすいVisual C++入門基礎講座
初心者のための世界で一番わかりやすいVisual 
Basic入門基礎講座