二分查找算法,基本思想很简单:利用查找待集合中元素的单调性,来一步步逼近目标元素的位置。例如,在一个升序排列的A数组中,查找值为w的元素位置。用位于数组中部的元素A[mid]和w比较:若A[mid]=w,则找到目标元素的位置;若A[mid]<w,则说明w在数组的右半边,下次查找将在右半边进行;若A[mid]>w,则说明w在左半边,下次查找在左半边进行。
虽然思想简单,可是想实现一个正确的二分查找算法还是有难度的,至少对我来说是这样。现在就来把二分查找算法整理一下,温故知新,也方便以后参考。
查找值的位置
这是最基本的二分查找,假定数组中没有重复元素,代码如下:
static int binarySearch(int[] array,int key)
{
int begin=0;
int end=array.length-1;
while(begin<=end)
{
int mid=begin+(end-begin)/2;
if(array[mid]==key)
return mid;
else if(array[mid]>key)
end=mid-1;
else
begin=mid+1;
}
return -1;
}
代码容易理解,但是有几点要注意
(1). 判断条件为begin<=end,容易写成begin<end。若是begin<end,在查找时,会出现错误。
错误原因:若查找最后一个元素时,进行到begin=n-2,end=n-1时,mid=(begin+end)/2,由于整数除是取地板,mid=begin。进入循环,A[mid]小于目标值,begin=mid+1, 则begin=end。这时候会直接结束循环,得出的结果是目标值不在数组中。
(2). begin和end的赋值,容易错写成begin=mid,end=mid. 这样写的错误不容易发现。你会发现查找前面n-1个元素都能得出正确
结果。但是查找最后一个元素以及不存在的元素时,程序会陷入死循环。
错误原因:查找最后一个元素以及不存在的元素时。到最后,begin=n-i,end=n-i+1,mid=begin(原因如上)。这时候,目标值大于mid, begin=mid, 回到之前的状态。begin一直小于end,因此会陷入死循环。
变体一 查找第一个目标值、最后一个目标值
上面的情况假定每个值在数组中只有一个,如果现在数组中可能会有多个相同的值呢?下面的代码会返回这些值中第一个和最后一个的位置。
static int binarySearch_firstX(int[] array,int x)
{
int begin=0,end=array.length-1,mid;
while(begin<end)
{
mid=begin+(end-begin)/2;
if(x<=array[mid])
{
end=mid;
}else
{
begin=mid+1;
}
}
if(array[end]==x) return end;
return -1;
}
static int binarySearch_lastX(int[] array,int x)
{
int begin=0,end=array.length-1,mid;
while(begin<end)
{
mid=begin+(end-begin+1)/2;
if(array[mid]<=x)
{
begin=mid;
}else
{
end=mid-1;
}
}
if(array[begin]==x) return begin;
return -1;
}
(1)binarySearch_firstX查找第一个x的位置,注意不是一找到等于x的值就立刻返回。查找x的第一个位置时,若x<=array[mid],则firstx该在mid的左边或者等于mid,因此end=mid;若x>array[mid],则firstx肯定在mid右边,得begin=mid+1。
注意循环的结束条件是begin<end。若误写成begin<=end,查询时会进入死循环。 错误原因:
1 . 若查询到最后出现begin=firstx,end=firstx+1,mid=(begin+end)/2----->mid=begin,则x=array[mid] ,end<---mid,end与begin相等,此后每次循环end=begin,死循环。
2 . 若查询到最后出现begin=firstx-1,end=first,mid=(begin+end)/2------->mid=begin,则x>array[mid],begin<---mid+1,end与begin相等,与上面同理,是死循环。
(2)binarySearch_lastX查找最后一个x的位置。查找最后一个x的位置时,若x>=array[mid],lastx在mid的右边或者等于mid,得到begin=mid;若x<array[mid],则lastx一定在mid的左边,得到end=mid-1。
循环结束条件是begin<end,并且要注意,mid不在是(begin+end)/2,而是(begin+end+1)/2,即这次不再是取地板了。
如果错写成前面一种情况,查询时会发生死循环。错误原因:
1 . 若查询到最后,出现begin=lastx,end=lastx+1,按照错误方式计算mid,得到mid=begin。A[mid]=x,继续将begin赋为mid,begin会在后面的循环中一直小于end,死循环。而用正确方式计算mid,得到mid=begin+1,此时A[mid]>x, end<---mid-1,得到end和begin相等,顺利跳出循环,并且end和begin都指向lastx。
2 . 查询到最后,出现begin=lastx-1,end=lastx。按照错误方式计算mid,得到mid=begin,A[mid]=x,继续将begin赋为mid,begin会在后面的循环中一直小于end,死循环。而用正确方式计算mid,mid=begin+1,A[mid]=x,begin<---mid,得到begin=end,顺利跳出循环,并且end和begin都指向lastx。
待续未完……