第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 | 1 | 2 | 3 | |
0 | 0 | 8 | 9 | 4 |
1 | 10 | 1 | 5 | 11 |
2 | 12 | 6 | 2 | 13 |
3 | 7 | 14 | 15 | 3 |
4次を例とすれば、9の位置にある場合です。
y座標が0で、x座標がn-2=4-2=2
ですから、座標(0,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 | 1 | 2 | 3 | |
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個存在しました、となってしまいます。
どうしてでしょうか。
それは座標(3,3)が存在するためです。
これも gj = n - 1 And gi > 0 満たしていますが、
残念がながら座標(3,0)座標(3,1)座標(3,2)
にはまだ数字が入っていませんから、
横合計を計算してはいけませんね。
g > n の条件を付け加えれば
座標(3,3)を対象から外すことができます。
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 | 1 | 2 | 3 | |
0 | 0 | 8 | 9 | 4 |
1 | 10 | 1 | 5 | 11 |
2 | 12 | 6 | 2 | 13 |
3 | 7 | 14 | 15 | 3 |
12の位置すなわち座標(2,0)の位置にいる場合です。
このとき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 はなぜ必要でしょうか。
これも座標(3,3)を対象から外すためです。
対象から外せれば何でも良いので、
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 | 1 | 2 | 3 | |
0 | 0 | 8 | 9 | 4 |
1 | 10 | 1 | 5 | 11 |
2 | 12 | 6 | 2 | 13 |
3 | 7 | 14 | 15 | 3 |
座標(3,0)と座標(3,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 |
というような番号付けを実現しましょう。
初心者のためのc++ vc++ c言語 入門 基礎から応用までへ
初心者のための excel 2007 2010 2013 vba マクロ 入門 基礎から応用まで
初心者のための世界で一番わかりやすいVisual C++入門基礎講座
初心者のための世界で一番わかりやすいVisual Basic入門基礎講座へ
vb講義トップへ
VB講義基礎へ
専門用語なしのC++入門へ
専門用語なしのJava入門へ
専門用語なしのVBA入門
数独のページ
魔方陣のページ
数学研究室に戻る
本サイトトップへ