关于二分查找,想必大家都非常熟悉了,正如《编程珠矶》中提到的,这是一个简单的程序,可是要实现一个无bug的版本确并非一件易事,笔者最近看了一下二分搜索,简短得概括一下。
对于不同的需求,我们面临的二分查找就会有不同的版本,就我最近遇到的几个版本好好解释一下:
- 给定一个数组,二分查找该数组中是否存在某元素elem,如果存在返回该元素的下标,如果不存在返回-1。
这是最常见的版本,代码也不是很难。最常见的代码如下:
在数组A[ left, right ]中寻找元素 m。
int binarySearch(int A[], int left, int right, int target)
{
int mid;
if(left > right)
{
cout << "find range error!" << endl;
return -1;
}
while(left <= right) //注意此处的数据范围的空间是[ left, right ]的闭区间,每次进入循环之后保持循环的循环不变式就可以了
{
mid = left + (right-left)/2; //避免溢出
assert( ( A[left] <= A[mid] ) && ( A[mid] <= A[right] )); //确保是有序的数组
if ( A[mid] == target ) return mid;
else if( A[mid] > target ) right = mid-1;
else left = mid + 1;
}
cout << target << " not exist in A" << endl;
return -1;
}
二分查找的递归实现:(虽然大部分时候不适用递归,递归的函数调用开销较大,,但是这里还是实现一个递归的版本。)
int binarySearchRecursion(int A[],int left, int right,int m)
{
if(left <= right)
{
int mid = left + (right-left)/2;
if(A[mid] == m) return mid;
else if(A[mid] > m)
{
right = mid-1;
binarySearchRecursion(A,left,right,m);
}
else
{
left = mid+1;
binarySearchRecursion(A,left,right,m);
}
}
//此处必须加上else,否则在递归调用返回之后,无论值为多少,最终都会是-1!!
else
{
return -1;
}
}
- 给定一个数组A,二分查找该数组中是否存在某元素elem,如果存在返回该元素第一次出现的下标,如果不存在返回-1。
注意:首先要确定在该数组中存在这样的元素elem,并且找到它的起始位置。代码如下:
int binarySearchStartPos(int A[], int left, int right, int m)
{
int mid, tmid ;
if(left > right)
{
cout << "find range error !!" << endl;
return -1;
}
while(left <= right)
{
mid = left + (right-left)/2;
if(A[mid] == m) //这里首先保证我一定能够找到这样的元素m,那么既然满足这样的条件,返回值就一定不能为-1.
{
tmid = mid;
//刚开始我想的是,循环不变式是:[left right]之间一定包含要寻找的元素m。
//这样不能保证循环的终止。每次我的left和right 并没有保证下一次循环时的元素个数减少!!!
right = mid-1;
while(left <= right)
{
mid = left + (right-left)/2;
if(A[mid] == m){right = mid-1;tmid = mid;}
else if (A[mid] < m) left = mid + 1;
else;
}
return tmid;
}
else if(A[mid] > m) right = mid-1;
else left = mid+1;
}
cout << m << "not exist in A " << endl;
return -1;
}
注意:这里的每一步的范围的缩小与上述二分查找是不一样的,在确定数组中存在元素m之后,每一步的范围都必须保证元素m一定要在考察的子数组中。
关于二分查找的优化问题也是很重要的,二分查找本身的时间复杂度已经是很好了,可是针对于不同的应用,可以采用不同的优化方式。
比如:(right – left)/2 可以使用移位来实现,又或者避免数组的上下标越界,使用 left + ( right – left ) /2 来实现。避免越界。