第12講 プロシージャの再帰的使用によって魔方陣を自動生成する
第10話 改良魔方陣自動生成ソフトのコード解説

コード主要部分再掲
    If h = 1 Then
      If gj = n - 2 And gi = 0 Then
        w = 0
        For j = 0 To n - 1
          w = w + x(gi, j)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If
    If h = 1 Then
      If gj = n - 1 And gi > 0 And g > n Then
        w = 0
        For j = 0 To n - 1
          w = w + x(gi, j)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If
    If h = 1 Then
      If gi = n - 2 And gj = 0 Then
        w = 0
        For j = 0 To n - 1
          w = w + x(j, gj)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If
    If h = 1 Then
      If gi = n - 1 And gj > 0 And g > 2 * n Then
        w = 0
        For j = 0 To n - 1
          w = w + x(j, gj)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If
    If h = 1 Then
      If gi = n - 1 And gj = 0 Then
        w = 0
        For j = 0 To n - 1
          w = w + x(j, n - 1 - j)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If
    If h = 1 Then
      If gi = n - 1 And gj = n - 1 Then
        w = 0
        For j = 0 To n - 1
          w = w + x(j, j)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If

解説
  gi = iz(g)
  gj = jz(g)

        ji = iz(j)
        jj = jz(j)
の4行は第8話で作った番号のy座標とx座標を代入しています。


    If h = 1 Then
      If gj = n - 2 And gi = 0 Then
        w = 0
        For j = 0 To n - 1
          w = w + x(gi, j)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If
では何をしているでしょうか。
gj = n - 2 And gi = 0 とは

   0
0 0 8 9 4
1 10 1 5 11
2 12 6 2 13
3 7 14 15 3

4次を例とすれば、9の位置にある場合です。
y座標で、x座標n-2=4-2=2
ですから、座標(

y座標x座標の順に並んでいることに注意してください。
配列が(
)となっているからです。)
で確かに9の位置にあることがわかります。
改良前の
    If h = 1 Then
      If gj = n - 1 Then
        w = 0
        For j = 0 To n - 1
          w = w + x(gi, j)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If
だと、8,9の位置に数字が入っていないのに、
1行目(y座標が0)を合計してしまいます。
これだともちろん失敗します。
1行目にすべての数字が入ってから合計させなければ、
いけないからです。

次に
    If h = 1 Then
      If gj = n - 1 And gi > 0 And g > n Then
        w = 0
        For j = 0 To n - 1
          w = w + x(gi, j)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If

   0
0 0 8 9 4
1 10 1 5 11
2 12 6 2 13
3 7 14 15 3

4列目の2行目以降すなわち11,13,3にいる場合の横合計をチェックしています。
If gj = n - 1 And gi > 0 And
g > n Then の部分は
If gj = n - 1 And gi > 0 Then
としても良さそうですが、
これだと魔方陣は0個存在しました、となってしまいます。
どうしてでしょうか。
それは座標(
)が存在するためです。
これも gj = n - 1 And gi > 0 満たしていますが、
残念がながら座標(座標(座標(
にはまだ数字が入っていませんから、
横合計を計算してはいけませんね。
g > n の条件を付け加えれば
座標()を対象から外すことができます。

    If h = 1 Then
      If gi = n - 2 And gj = 0 Then
        w = 0
        For j = 0 To n - 1
          w = w + x(j, gj)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If
については

   0
0 0 8 9 4
1 10 1 5 11
2 12 6 2 13
3 7 14 15 3

12の位置すなわち座標()の位置にいる場合です。
このとき1列目(x座標0)は4つとも数字がすでに入っていますから、
列合計をしなければならない訳です。

    If h = 1 Then
      If gi = n - 1 And gj > 0 And
g > 2 * n Then
        w = 0
        For j = 0 To n - 1
          w = w + x(j, gj)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If
は4行目の2列目以降の列合計をチェックしていますが、
g > 2 * n はなぜ必要でしょうか。
これも座標()を対象から外すためです。
対象から外せれば何でも良いので、
If gi = n - 1 And gj > 0 And g > n Then
でもよいのです。

    If h = 1 Then
      If gi = n - 1 And gj = 0 Then
        w = 0
        For j = 0 To n - 1
          w = w + x(j, n - 1 - j)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If
    If h = 1 Then
      If gi = n - 1 And gj = n - 1 Then
        w = 0
        For j = 0 To n - 1
          w = w + x(j, j)
        Next
        If w <> Int(n * (n * n + 1) / 2) Then h = 0
      End If
    End If
はぞれぞれ

   0
0 0 8 9 4
1 10 1 5 11
2 12 6 2 13
3 7 14 15 3

座標(と座標()の位置にあるときの
対角線合計をチェックしています。

第16講を用意していて、
実はもっと速い方法があることに気がつきました。
番号付けは

n=4のとき

0 8 9 4
10 1 5 11
12 6 2 13
7 14 15 3

n=5のとき

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

n=6のとき

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

n=7のとき

0 13 14 15 16 17 7
18 1 19 20 21 8 22
23 24 2 25 9 26 27
28 29 30 3 31 32 33
34 35 10 36 4 37 38
39 11 40 41 42 5 43
12 44 45 46 47 48 6

ではなく

n=4のとき

0 8 9 4
10 1 5 12
11 6 2 14
7 13 15 3

n=5のとき

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

n=6のとき

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

n=7のとき

0 13 14 15 16 17 7
18 1 23 24 25 8 26
19 27 2 31 9 32 33
20 28 34 3 37 38 39
21 29 10 40 4 43 44
22 11 35 41 45 5 47
12 30 36 42 46 48 6

と番号付けをした方が、
実は圧倒的に速いのです。
理由は、n=7のときを例に説明すると、

0 13 14 15 16 17 7
18 1 19 20 21 8 22
23 24 2 25 9 26 27
28 29 30 3 31 32 33
34 35 10 36 4 37 38
39 11 40 41 42 5 43
12 44 45 46 47 48 6

39まで行って、縦の合計が上手くいかない場合、
最悪の場合23辺りまで遡ってやり直さないと、
だめな場合があるのに対して、

0 13 14 15 16 17 7
18 1 23 24 25 8 26
19 27 2 31 9 32 33
20 28 34 3 37 38 39
21 29 10 40 4 43 44
22 11 35 41 45 5 47
12 30 36 42 46 48 6

ならせいぜい19辺りまで遡れば良いのです。
39から23まで遡るのと、
22にから19にまで遡るのでは、
39-22=17
22-18=4
と遡る差が全然違います。

0 13 14 15 16 17 7
18 1 19 20 21 8 22
23 24 2 25 9 26 27
28 29 30 3 31 32 33
34 35 10 36 4 37 38
39 11 40 41 42 5 43
12 44 45 46 47 48 6

だとかなり後まで行ってダメと宣告され、
大きく遡らなければならないのです。
実験はしていませんし、
コンピュータの性能から実験は現実的ではありませんが、
おそらく7次魔方陣辺りで、
倍加効果は1億倍を遙かに超えるでしょう。
では、皆さん
Sub zahyousakusei(n As Byte, iz() As Byte, jz() As Byte)
  Dim i As Byte, j As Byte, a(10, 10) As Byte, c As Byte
  For i = 0 To n - 1
    For j = 0 To n - 1
      a(i, j) = 128
    Next
  Next
  For i = 0 To n - 1
    a(i, i) = i
  Next
  c = n
  For i = 0 To n - 1
    If a(i, n - 1 - i) = 128 Then
      a(i, n - 1 - i) = c
      c = c + 1
    End If
  Next
  For i = 0 To n - 1
    For j = 0 To n - 1
      If a(i, j) = 128 Then
        a(i, j) = c
        c = c + 1
      End If
    Next
  Next
  For i = 0 To n - 1
    For j = 0 To n - 1
      iz(a(i, j)) = i
      jz(a(i, j)) = j
    Next
  Next
End Sub
の部分を改良して

0 13 14 15 16 17 7
18 1 23 24 25 8 26
19 27 2 31 9 32 33
20 28 34 3 37 38 39
21 29 10 40 4 43 44
22 11 35 41 45 5 47
12 30 36 42 46 48 6

というような番号付けを実現しましょう。







第9話へ 第11話へ



トップ

初心者のためのc++ vc++ c言語 入門 基礎から応用までへ
初心者のための excel 2007 2010 2013 vba マクロ 入門 基礎から応用まで
初心者のための世界で一番わかりやすいVisual C++入門基礎講座
初心者のための世界で一番わかりやすいVisual Basic入門基礎講座へ
vb講義トップへ
VB講義基礎へ
専門用語なしのC++入門へ
専門用語なしのJava入門へ
専門用語なしのVBA入門

数独のページ
魔方陣のページ
数学研究室に戻る
本サイトトップへ