課題4で完成させたプログラムの高速化を試みる。 元の関数等は消さずに残しておき、速度比較ができるようにする。 速度比較は盤面のサイズ n の変化に対し,全解探索に要する時間がど う変化するか調べること。
以下にはさまざまな高速化手法を示すが、 部分解の内容のコピーを避ける形で高速化を行うことを必須とする。 すべての高速化手法を試す必要はない。余力がある場合、他の実験履修者の 速度を上回りたい場合などに参考にすること。
[0, 1, 2, 3]
のように初期化しておく.
1行目に関して各桁への拡張を試みる場合は:
[0, 1, 2, 3], [1, 0, 2, 3], [2, 1, 0, 3], [3, 1, 2, 0]
のように,他の行の桁と入れ替える.次に,[1, 0, 2, 3]
の2行目に関して拡張する場合は,
[1, 0, 2, 3], [1, 2, 0, 3], [1, 3, 2, 0]
のように残りの行との間でのみ入れ替えを行えば,自動的に3通りのみ拡張を
試みることができる.
左上から右下に 右上から左下に 見た場合 見た場合 3 2 1 0 0 1 2 3 4 3 2 1 1 2 3 4 5 4 3 2 2 3 4 5 6 5 4 3 3 4 5 6
bm
から,
最下位の1ビットを取り出すには -bm & bm
とすればよい.
例えば,bm
=
(00000000000000000000000001101000)2 なら,-bm
=
(11111111111111111111111110011000)2 となるので
(つまり,bm + (-bm) のすべてのビットは 0), -bm & bm
=
(00000000000000000000000000001000)2 となる.
traverse(部分解 *a) { if(*aは解) return; forall (拡張方向 i) { if( *a を i方向に拡張可能 ) { *a そのもの(内容)を変更し,i方向に一段階拡張; traverse(a); *a への変更(i方向に一段階拡張)を元に戻す; } } }
+---+---+---+---+---+ | | | Q | | | +---+---+---+---+---+ | | | | | Q | <= いまここに置いた +---+---+---+---+---+ | | | | | | +---+---+---+---+---+ | | | | | | +---+---+---+---+---+ | | | | | | +---+---+---+---+---+と2行目に駒を置いた時点で,これを左回転した
+---+---+---+---+---+ | | Q | | | | +---+---+---+---+---+ | | | | | | +---+---+---+---+---+ | Q | | | | | +---+---+---+---+---+ | | | | | | +---+---+---+---+---+ | | | | | | +---+---+---+---+---+を考えると,この先,解まで拡張できたとしても,この先の置き方によらず, 左回転した解のほうが小さいことがわかるので,2行目をこのように置いた先の 探索を打ち切ればよい. (なお,この場合は,左右反転した解も小さいのでそれで打ち切ることも可能)
int f(int k, int *a){ if(k == 0) { return *a; } else { int i, s = 0; for(i=0;i<k;i++){ (*a) += i; s += f(k-1, a); (*a) -= i; } return s; } }という再帰呼出しを用いた計算は,明示的なスタックを用いることで
int f2(int k, int *a){ int st[10000]; int s = 0; int k0 = k; Ls: if(k == 0) { s += *a; } else { int i; for(i=0;i<k;i++){ (*a) += i; st[k--] = i; goto Ls; Lc: i = st[++k]; (*a) -= i; } } if(k == k0) return s; goto Lc; }のように変更することができる.