1.网友代码
二分查找或其扩展的问题及对应程序,内容如下:
1)二分查找元素key的下标,如无 return -12)二分查找返回key(可能有重复)第一次出现的下标,如无return -1
3)二分查找返回key(可能有重复)最后一次出现的下标,如无return -1
4)二分查找返回刚好小于key的元素下标,如无return -1
5)二分查找返回刚好大于key的元素下标,如无return -1
直接上代码:
/* binsearch 寻找key下标,不存在 return -1 */
int binsearch(int * arr, int lef, int rig, int key)
{
if(!arr) return -1;
int left = lef, right = rig;
while(left <= right)
{
int mid = left + ((right-left)>>1);
if(arr[mid] < key)
{
left = mid + 1;
}else if(arr[mid] > key)
{
right = mid - 1;
}else
return mid;
}
return -1;
}
/* binsearch_min 返回key(可能有重复)第一次出现的下标,如无return -1 */
int binsearch_min(int * arr, int lef, int rig, int key)
{
if(!arr) return -1;
int left = lef, right = rig;
while(left < right)
{
int mid = left + ((right-left)>>1);
if(arr[mid] < key)
{
left = mid+1;
}else
{
right = mid;
}
}
if(arr[left] == key) return left;//在退出循环的越界区间[right,left]考虑此处为什么用left
return -1;
}
/* binsearch_max 返回key(可能有重复)最后一次出现的下标,如无return -1 */
int binsearch_max(int * arr, int lef, int rig, int key)
{
if(!arr) return -1;
int left = lef, right = rig;
while(left < right -1)
{
int mid = left + ((right-left)>>1);
if(arr[mid] <= key)
{
left = mid;
}else
{
right = mid-1;
}
}
if(arr[right] == key) // 找max,先判断right
{
return right;
}else if(arr[left] == key)
{
return left;
}else
return -1;
}
/* binsearch_justsmall 返回刚好小于key的元素下标,如无return -1 */
int binsearch_justsmall(int * arr, int lef, int rig, int key)
{
if(!arr) return -1;
int left = lef, right = rig;
while(left < right - 1)
{
int mid = left + ((right-left)>>1);
if(arr[mid] < key)
{
left = mid;
}else
{
right = mid - 1;
}
}
if(arr[right] < key) // 找刚好小于,先判断right
{
return right;
}else if(arr[left] < key)
{
return left;
}else
return -1;
}
/* binsearch_justgreat 返回刚好大于key的元素下标,如无return -1 */
int binsearch_justgreat(int * arr, int lef, int rig, int key)
{
if(!arr) return -1;
int left = lef, right = rig;
while(left < right)
{
int mid = left + ((right-left)>>1);
if(arr[mid] <= key)
{
left = mid + 1;
}else
{
right = mid;
}
}
if(arr[right] > key) return right;//在退出循环的越界区间[right,left]上考虑此处为什么用right
return -1;}
对应的测试程序及结果请参考《二分查找,你真的会吗?》
2.二分代码原理分析
下面是本篇文章的重点,即以上所列二分查找代码原理细节思考:
1.为什么问题一的解法中while条件为小于等于?
从问题结果出发考虑,因为问题一需要找到等于K的位置,否则返回-1;而其他四个问题需要找到模糊位置,所以while条件中不能加等于条件;
2.为什么当解法中while条件为left<right时结果只用判断一个,而为left<right-1时需要判断两个呢?
因为当为left<right时,退出循环后区间[left,right]中只有一个元素;而为left<right-1时,退出循环后区间[left,right]中有两个元素;
3.为什么当寻找关于min的解法中while条件下不能使用if(arr[mid]<=key){ left=mid; }呢?
从问题结果出发考虑,因为此时如果mid左边还有等于K的值时,left不能移动回去,因为每次left都是只增不减,导致寻找最小值时左边边界确定失败。因此需要使用if(arr[mid]<key){ left=mid+1; }来确定准确的左边边界。
4.为什么当寻找关于max的解法中while条件下使用if(arr[mid]<=key){ left=mid; }呢?
从问题结果出发考虑,因为此时寻找的是最大的位置,此时如果arr[mid]等于key,则最大位置肯定包含在区间[mid,end)之间,如果使用left=mid+1同时left又是最后一个匹配的位置的时候,则导致寻找最大值时左边边界确定失败。因此需要使用if(arr[mid]<=key){ left=mid+1; }来确定准确的左边边界。
5.为什么当寻找关于max的解法中while条件使用left<right-1呢?
从if条件出发考虑,因为下面左边界的定位使用if(arr[mid]<=key){ left=mid; },导致当有两个元素的时候每次left可能无变化,这样在计算mid的时候值也无变化导致死循环。为了解决这个问题,使用left<right-1,判断区间[left,right]中是否还剩两个元素来避免死循环。
6.为什么问题二和问题四的解法中while条件及中间代码一样,但是最后先比较的对象不同呢?
从问题结果出发考虑,因为问题二要找等于K的最小位置,从右半边过来的right表示[right,end]都满足大于等于K,此时如果left越界处于[right,end],则根据循环条件退出,此时left所指向的位置刚好是第一个等于K的位置。
7.为什么当寻找关于min、justgreate时解法中while条件为left<right,而寻找max、justsmalll时解法中while条件为left<right-1呢?
从while下if条件是否导致死循环出发考虑,因为寻找关于min、justgreate时解法中if条件不会导致死循环,所以while条件为lef<right;而寻找max、justsmalll时解法中if条件会导致死循环,所以while条件为lef<right-1来避免;
8.整个二分查找的五个扩展问题解决思路所遵循的原则是什么?(总结)
1)先从最终结果的区间出发来考虑if语句中的写法;
2)先从while下的if条件是否会导致死循环出发来考虑while条件;(考虑只有两个元素的情况)
3)先从找min思考来推出max的解法;