说明:仅仅作回顾练习
话说Binary Serach是CSE中最基本最基本的问题,但是,《编程珠机》里号称“提供充足的时间,竟然仅有10%的专业程序员能将这个小程序编写正确”、“虽然第一篇二分搜索论文在1946年就发表了,但是第一个没有错误的二分搜索程序却直到1962年才实现”,确实很“触目惊心”。
这里只是本人复习中的一些思考过程,并写下来而已。
问题描述:
Given an integer X and integer A0,A1,...,An-1, which are presorted and already in memory, find i such that Ai=X, or return i=-1 if X is not in the input.
我们都知道,对于已经排序的输入,二分查找就是每一次迭代或递归将查找范围缩小一半。
首先,使用递归思考,写伪代码。
这是再典型不过的递归问题,逐渐分解为子问题,直到终止条件(这个问题的递归很容易分析其运行过程,所以看似和迭代的难度差不多,但递归会更自然很多,我觉得)。
int binarySearch(int Goal_Value, int array[], int low, int high)
{
Step 1:终止条件;
Step 2:分解为子问题;
}
Step2分解为子问题很简单了(刚开始就在这里想当然了),暂时将代码扩展为:
int binarySearch(int Goal_Value, int array[], int low, int high)
{
Step1:终止条件;
int middle = (low + high)/2;
if ( array[middle] < Goal_Value )
return binarySearch( Goal_Value, array, middle, high );
if ( array[middle] ==Goal_Value )
return middle;
if ( array[middle] > Goal_Value )
return binarySearch(Goal_Value, array, middle, high );
}
现在,将所有的难度转移到终止条件上了。
最开始,想到最极端的情况,二分查找的范围[low, high]缩小至下标相邻,即high=low+1。此时,由于middle = (high+low)/2,在计算机中,整除仍然等于low。
此时,考虑这几种情况:
a)low < X < high
b)high = X
c)low = X
d)X < low
e)X > high
终止条件可针对以上情况分别处理,但难点在于,可能还有些情况没有考虑到,为了简化处理,单独考虑high=low+1的情况即可。
代码如下:
// recursion
int binarySearch( int Goal_Value, std::vector<int> array, int low, int high )
{
if( 1 == (high-low) )
{
if( array.at(low) == Goal_Value )
return low;
if( array.at(high) == Goal_Value )
return high;
return -1;
}
int middle = (low+high)/2;
if( array.at(middle) > goal )
return binarySearch( goal, array, low, middle );
else if( array.at(middle) < Goal-Value )
return binarySearch( goal, array, middle, high );
else
return middle;
}
这个版本的有点是很简单,稍微想一会就可以实现,将难度降解到终止条件那几个条件判断语句,但缺点是,没有对查找范围精确控制,没有真正理解二分查找。
有了递归版本,转换成迭代版本就很容易了:
// iteration
int binarySearch2( int Goal_Value, std::vector<int> array )
{
int low = 0;
int high = array.size()-1;
while( (high-low) != 1 ) // exit
{
int middle = (low+high)/2;
if( array.at(middle) > Goal_Value )
high = middle;
else if( array.at(middle)< Goal_Value )
low = middle;
else
return middle;
}
if( array.at(low) == Goal_Value )
return low;
if( array.at(high) == Goal_Value )
return high;
return -1;
}
好,我们来看看标准库中的版本:
/*
* Performs the standard binary search using two comparisions per level.
* Returns index where item is found or -1 if not found.
* */
template<typename Comparable>
int binarySearch( const std::vector<Comparable> &array, const Comparable &Goal_Value)
{
int low = 0;
int high = array.size()-1;
while( low <= high )
{
int mid = ( low + high ) / 2;
if(array.at(mid) < Goal_Value )
low = mid + 1;
else if( array.at(mid) > Goal_Value )
high = mid -1;
else
return mid;
}
return -1;
}
其实,仔细想想,这确实是最正宗的。
如果array.at(mid) < Goal_Value,那么查找下界当然应该是mid+1,没必要留给下一次再去比较array[mid]与Goal_Value的值.
如果array.at(mid) > Goal_Value,那么查找上界当然应该是mid-1.
这么定义时,退出条件就可以是low <= high啦。
博客http://www.cppblog.com/converse/archive/2009/09/21/96893.html讲解了闭区间和开区间两种版本,会更好的理解二分查找。
只是记录下这个简单的过程,仅供参考。