(64.1) int 型の配列 a[1], a[2], ..., a[N] に、 自然数が格納されているとする。 これとは別に、ある int 型変数 x に、 ある自然数が代入されているとする。 以上が与えられたとき、a[i] の中に x と値の等しいものがあるか、 あるとしたらどれか(i.e. x == a[i] となる i はいくつか) を求める問題を、「探索」(search)という。(「検索」とも言うかもしれない。)
(64.2) 一般に、探索のほうが整列(ソート)よりもやさしい。 よって、普通は、探索のほうを先に習う。
(64.3) 配列 a[ ] に収められている値に何ら規則性がなければ、 端から順に調べてゆくしかない。 この場合の比較の回数は、見つかる場合で平均して N/2 であり、 見つからない場合は N である。
(64.4) もしも a[i] に数値が狭義単調増加に格納されている(i.e. a[1] < a[2] < ... < a[N] が成り立っている) とすると、端から順に調べるよりもはるかに効率のよい方法がある。
(64.5) それは、 「もしも x に等しいような a[i] があるとすればこことここの間(両端を含む)にある」 という範囲を、毎回毎回、約半分ずつにしてゆく方法である。
(64.6) その範囲の左の端の添え字を left, 右の端の添え字を right としよう。 最初の値はそれぞれ 1 と N である。 mid を、(left + right) / 2 と置く。 これは、その範囲のほぼまん中あたりを指す添え字である。そうしておいて、 x と a[mid] とを比べる。
(65.1) 次の書きかけプログラムを元に、各自で二分探索のプログラムを書け。
(65.2) 注意: ここでは、 見つからなかった場合に a[i] < x < a[i+1] を満たす a[i] と a[i+1] とを出力するようにするが、 x < a[0] だった場合、 および a[N] < x だった場合には、 (特別扱いをしなければ)a[0] や a[N+1] の値を出力してしまうことになる。 a[0] と a[N+1] を用意したのは、 この場合のことを考えてである。 探索の途中で a[0], a[N+1] にアクセスしてはいけない。 また、この場合もプログラムが正しく動くことを確かめるため、 関数 init() の中の「x = 」 の右辺を変更することでこのような場合を人為的に発生させ、チェックを行なうこと。
#include <stdio.h> #include <stdlib.h> /* srand() */ #include <time.h> /* time() */ #define N 25 void init(void); void print(void); int a[1+N+1]; /* これで a[0] から a[N+1] までが使える */ int x; /* これが探すべき数 */ main() { int left, right, mid; /* ここ(本体)を各自で考える */ } void init(void) { int i; { /* この中カッコの中(乱数の種の設定)はわからなくてもよい */ unsigned seed = (unsigned)time(NULL); /* 現在時刻を取得して */ printf("乱数の種は %u です.\n", seed); srand(seed); /* それを乱数の種に */ } /* ここに、a[0] = 0 から始めて、確率 1/2 で a[i+1] = a[i] + 1, 確率 1/2 */ /* で a[i+1] = a[i] + 2, という漸化式で a[N] までを埋めるコードを書く。 */ /* そうすれば、任意の自然数について、それが現れる確率は 1/2 となる。 */ a[N+1] = 0; /* 念のためこうしておく */ x = /* ここは、x が 1 から a[N]+1 までの値を */ /* とるよう、乱数を用いて決める */ return; /* この return は普通は書かない */ } void print(void) { /* ここはソートのときと全く同じでよい */ }
(65.3) 出力例1.(見つかった場合)
配列の大きさは 25 です. 乱数の種は 1243748801 です. 02 04 06 07 09 11 13 14 16 18 20 21 23 24 25 26 27 28 29 30 32 34 36 37 39 探すべき数は 18 です. 見つかるとしたら a[1] から a[25] までの中からです. 18 を a[13] = 23 と比べます. 見つかるとしたら a[1] から a[12] までの中からです. 18 を a[6] = 11 と比べます. 見つかるとしたら a[7] から a[12] までの中からです. 18 を a[9] = 16 と比べます. 見つかるとしたら a[10] から a[12] までの中からです. 18 を a[11] = 20 と比べます. 見つかるとしたら a[10] から a[10] までの中からです. 18 を a[10] = 18 と比べます. 18 は a[10] にありました.出力例2.(見つからなかった場合)
配列の大きさは 25 です. 乱数の種は 1243748840 です. 02 03 04 05 07 08 09 11 12 13 14 15 17 18 20 22 23 25 27 28 29 30 32 34 36 探すべき数は 26 です. 見つかるとしたら a[1] から a[25] までの中からです. 26 を a[13] = 17 と比べます. 見つかるとしたら a[14] から a[25] までの中からです. 26 を a[19] = 27 と比べます. 見つかるとしたら a[14] から a[18] までの中からです. 26 を a[16] = 22 と比べます. 見つかるとしたら a[17] から a[18] までの中からです. 26 を a[17] = 23 と比べます. 見つかるとしたら a[18] から a[18] までの中からです. 26 を a[18] = 25 と比べます. 26 は見つかりませんでした. 参考までに:a[18] = 25, a[19] = 27.
(65.4) レポートはいつものようにメールで、