第21講 末項確定法
第1話 末項確定法とは?

今回はいつもと手法を変えて、
最初に、今回の成果を最初に紹介した後に、
末項確定法とは何か説明しましょう。
以下のデータは各次魔方陣を100個作るのにかかった時間です。

1個あたり0.015912秒です。
前話のデータが0.07284秒ですから、約4.6倍のスピード差です。
さらに、10次魔方陣の場合

1個あたり0.398892秒ですから、約5.6倍です。
今までソフトがバージョンアップする度に、
1万倍とか1100万倍とかとう桁外れの性能アップを見せていたのに比べるとかなり地味に見えるかもしれませんが、
結構大きな戦果だと思います。
プログラミングの世界では、1.1倍でも大きな成果です。
(例えば、数独問題作成ソフトのスピードを1.1倍にするのに私は数日も場合によっては数週間費やしていました。
皆さんからすれば結果だけ見ているので、その苦労は見えないと思いますが、
プログラミングとは地味な研究の積み重ねなのです。
ただ、前々話で紹介したようにときには驚愕の成果を見せることもあります。)
ですから5,6倍はかなり大きな成果です。
さらに、今回の改良によって11次魔方陣も1個あたり約0.056秒でできています。
前話のときは途中で実験を打ち切ってしまったので、単純比較はできませんが、
20秒まで探索範囲を広げて、60分も実験を続けましたが最適乱数系列を発見できませんでしたので、
仮に前話のソフトが20秒であると前提しても、
20÷0.056=約360倍です。

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

末講確定法とは、水色以外のセルについては、初めから入る数字は確定していることに、
着眼した方法です。
例えば、

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

と来たときに、青のセルに入る数字は決まっています。

(表を見るときには、セルに入っている数字がセル番号のかそのセルに実際に入っている数字かを常に区別してください。
上表の数字は、セル番号ではなく、そのセルに入っている実際の数字です。
本講義においては、セル番号のときは基本的にはピンクとしています。)
6+4+9+4+1+0+8+3+4=39
対角線の合計は、
0+1+2+3+4+5+6+7+8+9=45
ですから、

に入る数字は
45−39=6
と確定してしまうのです。


ですから、このセルについてはfor文で探索するのは無駄です。

に来たら、for文は通らないで次の世界(g+1)に進むか、
前の世界(g−1)に戻るしかないのです。
戻る場合の言うのは、例えば

なら、6+4+9+4+1+0+8+0+2=34ですが、
合計の45との差をとると、45−34=11

にはいる最大数は9ですから、
このセルのある対角線の合計を45にするのは不可能になっているわけです。
この場合には1つ前のセル

に戻ってやり直すしかありません。



あるいは

の場合も6+4+9+4+1+2+8+5+7=46となり、


に入る数字が−1となってしまいこれも禁則に反します。
セルに入る数字は0から9までですから。
この場合にも

に戻ってやり直すしかありません。




さて、末講確定法についてはお分かりでしょうか。
ソースでは、
void f1(int g){
  if(s==100)return;
  int i,j,k,h,wa,kk,kkk,m;
  kk=rand()%n;
  m=n/2;
  for(i=0;i<n;i++){
    kkk=(kk+i)%n;
    a1[y[g]][x[g]]=kkk;
    h=1;
    cn1[kkk]++;
    if(cn1[kkk]>n)h=0;  
    if(h==1){
      if(g==n-1){
        wa=0;
        for(j=0;j<n;j++){
          wa+=a1[j][j];
        }
        if(wa!=n*(n-1)/2)h=0;
      }
    }
    
if(h==1){
      if(y[g]==n-1 && x[g]==0){
        wa=0;
        for(j=0;j<n;j++){
          wa+=a1[n-1-j][j];
        }
        if(wa!=n*(n-1)/2)h=0;
      }
    }

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

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

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

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

    }
    if(h==1){
      if(g<n*n-1){
        f1(g+1);
      }
      else{
        f2(0);
        if(s==100)return;
      }
    }
    cn1[kkk]--;
  }
}
緑色以外の色のついている部分を工夫しなければなりません。
これらは、for文の中にあってはいけませんから、for文の前に出すことになります。
するとfor文は、
  for(i=0;i<n;i++){
    kkk=(kk+i)%n;
    a1[y[g]][x[g]]=kkk;
    h=1;
    cn1[kkk]++;
    if(cn1[kkk]>n)h=0;
    if(h==1)f1(g+1);
    cn1[kkk]--;
  }
と至ってシンプルなものになります。


第20講第9話へ 第21講第2話へ


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