サーチのアルゴリズム (2) 二分探索法|新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門


2023-05-09 更新

基本情報技術者試験は、 2023 年 4 月から新制度で実施されています。

この連載では、新制度で採用された新しい擬似言語(旧制度の擬似言語とは、表記方法に違いがあります)を使って、試験のシラバス(情報処理技術者試験における知識・技能の細目)に示されている代表的なアルゴリズムとデータ構造を、プログラミング入門者向けにやさしく解説します。 受験対策としてだけでなく、アルゴリズムとプログラミングの基礎知識を習得するために、ぜひお読みください。

今回は、効率的なサーチアルゴリズムである 二分探索法 を取り上げます。

二分探索法のアルゴリズム

図 1 に示したように、裏返された状態で 7 枚のトランプが並んでいるとします。 これらの中から、マークは何でもよいので 5 のカードを見つけるとしたら、どうしますか? 「先頭(左端が先頭だとします)から順に 1 枚ずつめくって 5 かどうかをチェックする」と考えたなら、それで正解です。 そのアルゴリズムは、第 4 回の記事で紹介した「線形探索法」です。

それでは、これら 7 枚のトランプが昇順(小さい順)にソートされているとしたら、どうしますか? 線形探索法より効率的なアルゴリズムがありますので、ちょっと考えてみてください。 ポイントは、最初にどこをめくるかです。

図 1 裏返されたトランプの中から 5 を見つける

勘のいい人は「真ん中のカードをめくって、その値から探索対象を前側か後ろ側に絞り込む」というアルゴリズムを思い付いたでしょう。 それで正解です。 そのアルゴリズムを「二分探索法」と呼びます。

トランプ(プログラムでは配列)がランダムに並んでいたら、線形探索法を使うことになりますが、ソートされているなら二分探索法を使った方が効率的です。 7 枚のトランプがある場合には、線形探索法では最大で 7 回カードをめくりますが、二分探索法では最大で 3 回カードをめくるだけで済みます。

図 2 に二分探索法で目的の値が見つかるまでの手順の例を示します。

図 2 二分探索法で目的のカードが見つかるまでの手順の例



7 枚のカードの探索対象は、 7 枚 → 3 枚 → 1 枚 と二分割されていきます。 だから二分探索と呼ぶのです。

ここでは、最後の 1 枚で見つかっていますが、その前の段階で見つかることも、最後までチェックして見つからないこともあります。

二分探索法のプログラムの例

リスト 1 は、二分探索法のプログラムの例です。 要素数 7 個の整数型の配列 a (昇順にソートされていて、要素番号は 0 から始まるとします)の中から変数 x と同じ値を見つけます。

二分探索法のプログラムには、決まりきった書き方があります。 これを、空手の型のように、プログラムの型として覚えてください。

なお、第 4 回の記事で説明したサーチアルゴリズムの基本である

format_quote

「見つかった場合は、見つかった位置(要素番号)を探索の結果とする」
「見つかった場合は、その時点で探索を終了する」
「見つからなかった場合は、配列の要素番号としてあり得ない -1 を探索の結果とする」

format_quote

は、二分探索法でも同様です。 繰り返しを中断するのに、 break 文を使うことも使わないこともできるのも、線形探索法と同様です。 リスト 1 では、 break 文を使っていません。

swipeプログラムは横スクロールできます

リスト 1code 二分探索法のプログラムの例(その 1 )
整数型の配列 a{ 1, 2, 5, 6, 8, 10, 12 }
整数型 x, pos, left, mid, right

// 変数に初期値を設定する
x5		// 見つける値
pos-1	// 探索の結果
left0	// 探索対象の左端の要素番号
right6	// 探索対象の右端の要素番号

// まだ探索対象があり、かつ、まだ見つかってない限り、探索を繰り返す
while (leftright and pos = -1)
mid(left + right) ÷ 2	// 真ん中の要素の要素番号を得る
  if (a[mid] = x)		// 真ん中の要素が見つける値と同じ場合
    posmid			// 探索の結果に見つかった位置を設定する
  elseif (a[mid] > x)		// 真ん中の要素が見つける値より大きい場合
    rightmid - 1		// 真ん中より前側に探索対象を絞り込む
  else				// 真ん中の要素が見つける値より小さい場合
    leftmid + 1		// 真ん中より後側に探索対象を絞り込む
  endif
endwhile

// 探索の結果を表示する
print(pos)

リスト 1 の内容を詳しく説明しましょう。

配列 a から変数 x と同じ値を見つけます。 探索の結果は、変数 pos に格納します。 ここでは、変数 x に 5 を設定しています。 変数 pos は、見つからなかったことを意味する -1 で初期化しています。

探索によって、 5 は a[2] に見つかるので、変数 pos の値は 2 で上書きされます。 最後にある print は、引数の値を画面に表示する手続だとします。 print(pos) によって、探索の結果が画面に表示されます。ここまでは、第 4 回で紹介した線形探索のプログラムと同様です。

二分探索法の型

変数 left 、 mid 、 right は、探索対象の左端、真ん中、左端の要素番号です。 これら 3 つの変数を使うことが、二分探索法の型です。 最初は、配列全体が探索対象なので、変数 left に先頭の要素番号の 0 を設定し、変数 right に末尾の要素番号の 6 を設定します。

 

二分探索法では、 while 文を使った繰り返しを行います。 while 文のカッコの中に記述する

leftright and pos = -1

という条件も、二分探索法の型です。 これは、「まだ探索対象があり、かつ、まだ見つかってない」という条件を意味しています。

pos = -1 が「まだ見つかっていない」という条件であることはわかると思いますが、どうして leftright が「まだ探索対象がある」という条件になるか、わかりますか?

leftの初期値は配列の先頭であり、 right の初期値は配列の末尾なので

left < right

です。 while文の繰り返しで、徐々に探索対象を狭めていくと、最後の 1 個の要素をチェックするときに

left = right

になります。 したがって、 left < right から left = right までは探索対象があるので、これらをまとめて記述して

leftright

になるのです。

 

while 文で繰り返される処理の内容も、二分探索法の型です。

まず

mid(left + right) ÷ 2

で、現在の探索対象の真ん中の要素番号を変数 mid に得ています。 真ん中は (left + right) ÷ 2 という計算で得られます。 変数 left とright は、整数型なので、割り算の小数点以下はカットされます。

次に、 if 文で真ん中の a[mid] と変数 x の値を比べて、処理を 3 つに分岐させます。

looks_one もしも、真ん中の要素が見つける値と同じ場合 a[mid] = x
arrow_forward 探索の結果に見つかった位置を設定します posmid
looks_two そうではなく、もしも、真ん中の要素が見つける値より大きい場合 a[mid] > x
arrow_forward 真ん中より前側に探索対象を絞り込みます rightmid - 1
looks_3 そうではない場合 else
arrow_forward 真ん中の要素が見つける値より小さい場合なので、真ん中より後側に探索対象を絞り込みます leftmid + 1

二分探索法の穴埋め問題にチャレンジする

試験対策として、二分探索法の穴埋め問題にチャレンジしてみましょう。

リスト 2 は、要素数 10 個(これまでの例より要素数が多くなっていることに注意してください)の配列 a から変数 x と同じ値を二分探索法で見つけるプログラムです。 プログラム中の に入る正しい答えの組合せを解答群の中から選んでください。 配列は、昇順にソートされていて、要素番号は 0 から始まるとします。

リスト 2code 二分探索法のプログラムの例(その 2 )
整数型の配列 a{ 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13 }
整数型 x, pos, left, mid, right

x8
pos-1
left0
righta

while (b)
mid(left + right) ÷ 2
  if (a[mid] = x)
    posmid
  elseif (a[mid] > x)
    rightmid - 1
  else
    leftmid + 1
  endif
endwhile

print(pos)

解答群

a b
9 left ≦ right and pos = -1
9 right ≦ left and pos = -1
10 left ≦ right and pos = -1
10 right ≦ left and pos = -1

空欄 a は、探索対象の右端の要素番号に、配列の末尾の要素番号を設定する処理です。 ここでは、配列の要素数が 10 個で、要素番号は 0 から始まるので、末尾の要素番号は 9 です。

空欄 b は、探索を繰り返す条件です。 ここには、「まだ探索対象があり、かつ、まだ見つかってない」を意味する

leftright and pos = -1

を指定します。 したがって、選択肢アが正解です。

今回は、効率的なサーチアルゴリズムである二分探索法を取り上げました。

次回は、再帰のアルゴリズムを取り上げる予定です。

それでは、またお会いしましょう!

label 関連タグ
科目A試験は、
免除できます。
独習ゼミで科目A試験を1年間免除して、科目B試験だけに集中しましょう。
免除試験を受けた 74.9% の方が、
科目A免除資格を得ています。
科目A免除試験 最大 2 回の
受験チャンス !
info_outline
科目A免除試験 最大 2 回の
受験チャンス !
詳しく見てみるplay_circle_filled
label これまでの『新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門』の連載一覧

マージソート|新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門

update

二分木の深さ優先探索と幅優先探索|新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門

update

比べてわかるキューとスタックの仕組み|新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門

update

「リスト」というデータ構造|新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門

update

サーチのアルゴリズム (3) ハッシュ表探索法のアルゴリズム|新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門

update

クイックソートのアルゴリズム|新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門

update

再帰呼び出しのアルゴリズム|新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門

update

サーチのアルゴリズム (2) 二分探索法|新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門

update

サーチのアルゴリズム (1) 線形探索法|新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門

update

多重ループを使うバブルソート|新しい擬似言語で学ぶ 科目 B アルゴリズムとプログラミング入門

update
label 著者