二分で書けない二分探索
C言語で二分探索をつくってみました
#include<stdio.h> #include<time.h> #include<stdlib.h> #define MAX 100 int binaly_search(int array[],int first,int last,int n) { int mid = (first+last)/2; if(first+1 == last){ if(array[last] == n) return last; else return -1; } else if(array[mid] >= n) return binaly_search(array,first,mid,n); else return binaly_search(array,mid,last,n); } int linear_search(int array[],int n,int size) { int i; for(i=0;i<size;i++) if(array[i]==n) return i; return -1; } void quicksort(int array[],int first,int last) { int i=first,j=last,std = array[(first+last)/2],tmp; while(1){ while(array[i] < std) i++; while(array[j] > std) j--; if(i >= j) break; tmp = array[j]; array[j] = array[i]; array[i] = tmp; i++; j--; } i--; j++; if(i-first > 0) quicksort(array,first,i); if(last-j > 0) quicksort(array,j,last); } int main() { int target,i,array[MAX]; srand((unsigned)time(NULL)); for(i=0;i<MAX;i++) array[i] = rand() % MAX; //整列 quicksort(array,0,MAX-1); for(i=0;i<MAX;i++) printf("array[%d] = %d\n",i,array[i]); printf("\n"); target = rand() % MAX; //線形探索 printf("Search : %d by linear search.\n",target); if((i = linear_search(array,target,sizeof(array)/sizeof(int))) == -1) printf("%d is not found.\n",target); else printf("%d is in array[%d].\n",target,i); //二分探索 printf("Search : %d by binaly search.\n",target); if((i = binaly_search(array,0,MAX-1,target)) == -1) printf("%d is not found.\n",target); else printf("%d is in array[%d].\n",target,i); return 0; }
二分探索部分は再帰を用い、範囲をせばめてfirst部とlast部が隣接したときに
last部を比較し、nと一致したらlastを返すようにしました。
配列ではじめにnが現れる場所を探し、
一致するものがないとき、-1を返します。
乱数配列を適当につくってクイックソートで整列し、探索対象も乱数で指定してサーチするようにしています。
二分探索の結果を確認するために、リニアサーチも実装しました。
これを実行すると、
$ ./binaly array[0] = 1 array[1] = 1 array[2] = 2 array[3] = 3 ... array[83] = 84 array[84] = 85 array[85] = 86 ... array[99] = 98 Search : 85 by linear search. 85 is in array[84]. Search : 85 by binaly search. 85 is in array[84].
こんな感じになります
しかし、探索対象が0のとき
$ ./binaly array[0] = 0 array[1] = 1 array[2] = 4 array[3] = 6 ... array[98] = 96 array[99] = 98 Search : 0 by linear search. 0 is in array[0]. Search : 0 by binaly search. 0 is not found.
二分探索側で0が発見できません...コマリマシタネエ
二分探索のコードを見直すと、
int binaly_search(int array[],int first,int last,int n) { int mid = (first+last)/2; if(first+1 == last){ if(array[last] == n) return last; else return -1; } else if(array[mid] >= n) return binaly_search(array,first,mid,n); else return binaly_search(array,mid,last,n); } int linear_search(int array[],int n,int size) { int i; for(i=0;i<size;i++) if(array[i]==n) return i; return -1; }
そもそものアルゴリズムを見直してみると、
[1]mid = (first+last)/2 とする(小数点切り捨て)。
[2]first+1==last のとき、array[last]がnと等しければlastを返し、等しくなければ-1を返す。そうでなければ、array[mid]が目的値 nより小さいとき firstをmidとして[1]へ、それ以外のときlastをmidとして[1]へ
この条件を繰り返し、first+1==lastとなるとき、lastはarray[last] >= nを満たしています。firstはその逆のarray[first] < nを満たしています。firstとlastはもとはmidだった数なので、array[mid]として比較されているからです。このとき、配列はソートされていて単調なため、array中にnが存在するならば、array[last]はnを指しているはずです。言い換えると、firstは条件(array[a] < n)を満たす最大のa、lastは条件(array[a] >= n)を満たす最小のaとなります。
例えば、不等号を少しいじって else if(array[mid] > n)とすると、lastは条件(array[a] > n)を満たす最大のaを指すようになり、firstは(array[a] <= n)を満たす最大のaを指すようになります。このとき、nが存在するならばarray[first]はnを指しています。nが複数あるときは、その最後尾のnを指しています。
しかし、array[first] <= array[last]かつfirst < lastなので
array中に0が一つしかないとき、array[last]には配列で最も小さい数が入ることはありません
条件を逆にしたときも同様の問題で、そのときは配列で最も大きい数値を探すことができません。
これを解決するために、array[first]とnを比較してから、array[last]と比較するようにしました。
int binaly_search(int array[],int first,int last,int n) { int mid = (first+last)/2; if(first+1 == last){ if(array[first] == n) return first; if(array[last] == n) return last; else return -1; } else if(array[mid] >= n) return binaly_search(array,first,mid,n); else return binaly_search(array,mid,last,n); }
target = array[0]として実行結果
$ ./binaly array[0] = 0 array[1] = 0 ... array[98] = 96 array[99] = 99 Search : 0 by linear search. 0 is in array[0]. Search : 0 by binaly search. 0 is in array[0].
array[last]と比較してからarray[first]と比較するようにすると、上の実行例のように0が2つ連続しているときに、last==1なので、array[last]==nを満たし、1が出力されてしまいます。値がnである配列の一番小さい添字を印字するようにしたかったので、この順番になりました。ほとんどの場合、firstは出力されないから無駄な処理になってしまうのがなんとも...
なんかもっといい方法はないですかね