今回は065 データ構造(配列5)で作成した探索(サーチ)の一種で「二分探索(バイナリーサーチ)」を作ってみます。
まずは考え方から説明します。
前提条件として、データがソートされている必要があります。
※ソートは昇順(小さい順)でも降順(大きい順)でも構いません。
この配列に「55」があるかどうか調べます。
「線形探索(シーケンシャルサーチ)」では左端から順番に探していましたが、「二分探索」では真ん中から調べます。
しかし、データは10個あるため、「真ん中」と言われてもどこかわかりません。
そこで「真ん中」を計算するため、以下の式を使います。
(最初の添え字 + 最後の添え字) ÷ 2
上の図に当てはめてみると、
(0 + 9) ÷ 2 = 4.5
となります。
添え字は整数ですから「4.5」は使えませんので、小数点以下を切り捨てた「4」が「真ん中」になります。
※C言語では、int型での計算は小数点以下切り捨てになりますから簡単ですよね。
では、「真ん中」の数値を見てみましょう。
「45」ですね。
探しているデータの「55」よりも小さい値です。
ということは、「45」以下のデータはこれ以上探す必要が無いということになります。
次に残ったデータのどこを調べるかというと、やはり「真ん中」です。
添え字の5から9までのデータが残っていますので、先ほどの式に当てはめると、
(5 + 9) ÷ 2 = 7
となります。
新しい「真ん中」のデータを見ると「76」で「55」よりも大きいです。
ということは、「76」以上のデータはこれ以上探す必要が無いということになります。
残ったデータから「真ん中」を調べます。
(5 + 6) ÷ 2 = 5 ※小数点以下切り捨て
5番目の添え字の中身は「55」ですので、探していたデータが見つかりました。
このように、データの「真ん中」を調べていくことで、探索回数をかなり減らすことができます。
今回使ったデータのように10個の要素があれば、
探索方法 | 最大 | 平均 |
---|---|---|
逐次探索 | 10回 | 5回 |
二分探索 | 4回 | 3回 |
で探すことが出来ます。
もし、1000個のデータであれば、
探索方法 | 最大 | 平均 |
---|---|---|
逐次探索 | 1000回 | 500回 |
二分探索 | 10回 | 9回 |
となり、差は歴然です。
※ただし、データがソートされている必要があります。
この説明だけではプログラムを書くことは難しいでしょう。
特に「見つからなかった」場合をどのように考えていくのかがはっきりしていません。
そのために、プログラム作成を意識してもう少し細かく説明します。
上の「55」を探す考え方を、詳細に書いてから「見つからなかった」場合を書きます。
左端の添え字を入れる変数を「left」、右端の添え字を入れる変数を「right」とします。
真ん中の添え字は「center」に入れることにします。
真ん中の添え字を計算する式をC言語風に書き換えると、
center = (left + right) / 2;
です。
最初のcenterは「4」になるはずですから、
となります。
dataのcenter番目(C言語風だとdata[center])は「45」ですから、目的の「55」よりも小さいです。
「45」以下は不要になり、残ったデータの中から「真ん中」を調べるのですが、データの左端を変更しなければなりません。
現在の「left」は「0」ですが、「45」までが不要になるので、「left」を「5」に変更します。
「5」という添え字はcenter「4」の右隣ですから、
left = center + 1;
で計算できます。
残ったデータの「真ん中」を再度計算します。
center = (left + right) / 2;
今度の「真ん中」は「7」になります。
「真ん中」のデータは「76」ですから、目的の「55」よりも大きいです。
「76」以上は不要になりますので、データの右端である「right」をcenterの左隣に変更します。
計算式は、
right = center - 1;
となります。
再度、残ったデータの「真ん中」を計算します。
center = (left + right) / 2;
今度の「真ん中」は「5」になり、目的の「55」を発見出来ました。
次に「見つからなかった」場合の説明をします。
「56」というデータがあるかどうか調べることにします。
途中までは上の説明と同じ動作をしますので、↓の状況から説明を続けます。
この状況はleftが「5」、rightが「6」、centerが「5」という状況です。
centerが指しているのは「55」というデータですから、「56」より小さいです。
「55」は不要になりますので、leftをcenterの右隣に移動します。
left = center + 1;
新しい「真ん中」を計算すると、
center = (left + right) / 2;
「6」になります。
centerが指しているのは「67」ですから、目的の「55」よりも大きいです。
「67」は不要になり、rightをcenterの左側に移動します。
ここで「left」と「right」が逆転しました。
これが「見つからなかった」状態になります。
では、手順をまとめます。
1.leftに左端の添え字を入れ、rightに右端の添え字を入れる 2.探したいデータを入力する 3.centerを計算する 4.dataのcenter番目と目的の数値を比較する @同じ場合 データは見つかり、終了する A目的の数値の方が大きい場合 leftをcenterの右隣に変更して3に戻る B目的の数値の方が小さい場合 rightをcenterの左隣に変更して3に戻る 5.leftとrightが逆転した場合、データは見つからず、終了する |
説明が長くなりましたが、手順をしっかりと考えなければプログラムはまともに動きません。
複雑なプログラムを組むためには手順をはっきりとさせる必要があります。
いきなりプログラムを打ち込むよりも、まずは考え方や手順を明確にすることに力をいれましょう。
それでは、プログラムを作ります。
手順のイメージがはっきりしている方は、自分で作ってみてください。
そうでない方は、少しずつ作って完成させていきます。
元データ↓
<sample program 070-00>
#include <stdio.h> #define COUNT 10 int main(void) { int data[COUNT] = { 9, 12, 23, 32, 45, 55, 67, 76, 88, 98 }; int left; int right; int center; return 0; } |
<実行結果1>
探したいデータを入力してください:98 発見! 続行するには何かキーを押してください・・・
<実行結果2>
探したいデータを入力してください:33 発見できず・・・ 続行するには何かキーを押してください・・・
<実行結果3>
探したいデータを入力してください:12 発見! 続行するには何かキーを押してください・・・
まずは、手順1を作成します。
1.leftに左端の添え字を入れ、rightに右端の添え字を入れる
<sample program 070-01>
#include <stdio.h> #define COUNT 10 int main(void) { int data[COUNT] = { 9, 12, 23, 32, 45, 55, 67, 76, 88, 98 }; int left; int right; int center; left = 0; right = COUNT - 1; return 0; } |
次に手順2を作成します。
2.探したいデータを入力する
<sample program 070-02>
#include <stdio.h> #define COUNT 10 int main(void) { int data[COUNT] = { 9, 12, 23, 32, 45, 55, 67, 76, 88, 98 }; int left; int right; int center; int input; left = 0; right = COUNT - 1; printf("探したいデータを入力してください:"); scanf("%d", &input); return 0; } |
手順3を作成します。
3.centerを計算する
<sample program 070-03>
#include <stdio.h> #define COUNT 10 int main(void) { int data[COUNT] = { 9, 12, 23, 32, 45, 55, 67, 76, 88, 98 }; int left; int right; int center; int input; left = 0; right = COUNT - 1; printf("探したいデータを入力してください:"); scanf("%d", &input); center = (left + right) / 2; return 0; } |
手順4の@を作成します。
4.dataのcenter番目と目的の数値を比較する
@同じ場合 データは見つかり、終了する
<sample program 070-04>
#include <stdio.h> #define COUNT 10 int main(void) { int data[COUNT] = { 9, 12, 23, 32, 45, 55, 67, 76, 88, 98 }; int left; int right; int center; int input; left = 0; right = COUNT - 1; printf("探したいデータを入力してください:"); scanf("%d", &input); center = (left + right) / 2; if (data[center] == input) { printf("発見!\n"); } return 0; } |
手順4のAを作成します。
4.dataのcenter番目と目的の数値を比較する
A目的の数値の方が大きい場合 leftをcenterの右隣に変更して3に戻る
※手順3に戻るためループを付け加えます。
<sample program 070-05>
#include <stdio.h> #define COUNT 10 int main(void) { int data[COUNT] = { 9, 12, 23, 32, 45, 55, 67, 76, 88, 98 }; int left; int right; int center; int input; left = 0; right = COUNT - 1; printf("探したいデータを入力してください:"); scanf("%d", &input); for(;;) { center = (left + right) / 2; if (data[center] == input) { printf("発見!\n"); break; } if (data[center] < input) { left = center + 1; } } return 0; } |
手順4のBを作成します。
4.dataのcenter番目と目的の数値を比較する
B目的の数値の方が小さい場合 rightをcenterの左隣に変更して3に戻る
<sample program 070-06>
#include <stdio.h> #define COUNT 10 int main(void) { int data[COUNT] = { 9, 12, 23, 32, 45, 55, 67, 76, 88, 98 }; int left; int right; int center; int input; left = 0; right = COUNT - 1; printf("探したいデータを入力してください:"); scanf("%d", &input); for(;;) { center = (left + right) / 2; if (data[center] == input) { printf("発見!\n"); break; } if (data[center] < input) { left = center + 1; } if (data[center] > input) { right = center - 1; } } return 0; } |
最後に手順5を作成します。
5.leftとrightが逆転した場合、データは見つからず、終了する
<sample program 070-07>
#include <stdio.h> #define COUNT 10 int main(void) { int data[COUNT] = { 9, 12, 23, 32, 45, 55, 67, 76, 88, 98 }; int left; int right; int center; int input; left = 0; right = COUNT - 1; printf("探したいデータを入力してください:"); scanf("%d", &input); for(;;) { center = (left + right) / 2; if (data[center] == input) { printf("発見!\n"); break; } if (data[center] < input) { left = center + 1; } if (data[center] > input) { right = center - 1; } if (left > right){ printf("発見できず・・・\n"); break; } } return 0; } |
<実行結果1>
探したいデータを入力してください:98 発見! 続行するには何かキーを押してください・・・
<実行結果2>
探したいデータを入力してください:33 発見できず・・・ 続行するには何かキーを押してください・・・
<実行結果3>
探したいデータを入力してください:12 発見! 続行するには何かキーを押してください・・・
無限ループを使わず、while文で作成したバージョンも書いておきます。
<sample program 070-08>
#include <stdio.h> #define COUNT 10 int main(void) { int data[COUNT] = { 9, 12, 23, 32, 45, 55, 67, 76, 88, 98 }; int left; int right; int center; int input; left = 0; right = COUNT - 1; printf("探したいデータを入力してください:"); scanf("%d", &input); while (left <= right) { center = (left + right) / 2; if (data[center] == input) { printf("発見!\n"); break; } if (data[center] < input) { left = center + 1; } if (data[center] > input) { right = center - 1; } } if(left > right) { printf("発見できず・・・\n"); } return 0; } |
<実行結果1>
探したいデータを入力してください:98 発見! 続行するには何かキーを押してください・・・
<実行結果2>
探したいデータを入力してください:33 発見できず・・・ 続行するには何かキーを押してください・・・
<実行結果3>
探したいデータを入力してください:12 発見! 続行するには何かキーを押してください・・・
考え方や手順のことをアルゴリズムとも呼びます。
プログラミングで大事なのは言語自体の文法ではなく、このアルゴリズムです。
アルゴリズムを理解していれば、言語が変わってもプログラムは組めます。
配列を使った基本的なアルゴリズムはしっかりと覚えておき、何も見なくても作成出来るようになってください。