上一篇:数据结构与算法系列十八(二分查找1) 中,我们分享了二分查找算法的思想,并且通过循环和递归方式都实现了一遍。二分查找的思想非常容易理解(折半查找),相信你都已经掌握了。
但是你还记得吗?我们在上一篇留下了两个思考题:
-
二分查找的代码实现,需要注意的细节事项 (如何写出高效,且没有问题的二分查找代码)
-
如果待查找的目标数组中,有重复的数字,二分查找该如何实现呢?
- 比如:查找第一个值等于目标值的元素
- 比如:查找最后一个值等于目标值的元素
- 比如:查找第一个大于等于目标值的元素
- 比如:查找最后一个小于等于目标值的元素
#考考你:
1.你知道如何写出高效、且没有问题的二分查找代码吗?
2.你能写出二分查找的常见变形代码吗(有重复元素)?
案例
二分查找代码实现细节
#二分查找代码实现,需要关心的细节:
1.中位数计算问题
【问题引导】:
1.我们知道,二分查找涉及到三个数值的变化
2.低位:low,从0开始
3.高位:high,从n-1开始(n是数组元素个数)
4.中位数:mid,常规写法:mid = (low + high) / 2
【问题】:
mid = (low + high) / 2 写法有问题吗?
【解答】:
1.有问题,假设low,high数值足够大,那么low + high有可能超出int
的最大范围,会有溢出的风险
2.推荐写法:mid = low + (high - low) / 2
3.更高效的写法:mid = low + ((high - low) >> 1)。移位操作对计算机更友好,性能更高
2.下一次查找,低位、高位数变换
【问题引导】:
1.如果本次没有找到目标值,则需要进入下一次二分查找
2.根据需要变更低位值:low,或者高位值:high,进行折半操作
3.你是否会习惯性的写成:low = mid;或者high = mid
【问题】:
请问low = mid;或者high = mid有问题吗?
【解答】:
1.有问题,low = mid,或者high = mid会引起死循环的问题
2.正确写法:low = mid + 1;或者high = mid - 1
二分查找变形(重复元素)
查找第一个值等于目标值的元素
/**
* 查找第一个值等于目标值的元素
* @param array 目标数组
* @param n 目标数组大小
* @param target 目标值
* @return 目标值索引
*/
public static int binarySearchFirst(int[] array,int n,int target){
// 低位、高位
int low = 0;
int high = n - 1;
while(low <= high){
// 计算中位数
int mid = low + ((high - low) >> 1);
if(array[mid] > target){
high = mid - 1;
}else if(array[mid] < target){
low = mid + 1;
}else{
// 如果mid等于0,说明第一个元素就是目标值
// 如果mid不等于0,但是array[mid-1] 不等于目标值
// 说明此时mid也是要找的目标位置
if(mid == 0 ||
array[mid - 1] != target){
return mid;
}
// 否则,继续查找
high = mid - 1;
}
}
return -1;
}
查找最后一个值等于目标值的元素
/**
* 查找最后一个值等于目标值的元素
* @param array 目标数组
* @param n 目标数组大小
* @param target 目标值
* @return 目标值索引
*/
public static int binarySearchLast(int[] array,int n,int target){
// 低位、高位
int low = 0;
int high = n - 1;
while(low <= high){
// 计算中位数
int mid = low + ((high - low) >> 1);
if(array[mid] > target){
high = mid - 1;
}else if(array[mid] < target){
low = mid + 1;
}else{
// 如果mid等于n-1,说明最后一个元素就是目标值
// 如果mid不等于n-1,但是array[mid+1] 不等于目标值
// 说明此时mid也是要找的目标位置
if(mid == (n-1) ||
array[mid + 1] != target){
return mid;
}
// 否则,继续查找
low = mid + 1;
}
}
return -1;
}
查找第一个值大于等于目标值的元素
/**
* 查找第一个值大于等于目标值的元素
* @param array 目标数组
* @param n 目标数组大小
* @param target 目标值
* @return 目标值索引
*/
public static int binarySearchFirst_1(int[] array,int n,int target){
// 低位、高位
int low = 0;
int high = n - 1;
while(low <= high){
// 计算中位数
int mid = low + ((high - low) >> 1);
if(array[mid] < target){
low = mid + 1;
}else {
// 如果mid==0,则第一个元素就是查找的目标位置
// 如果mid!=0,如果array[mid-1] < target,则mid就是要查找的目标位置
// 否则,继续查找
if(mid == 0 ||
array[mid - 1] < target){
return mid;
}
high = mid - 1;
}
}
return -1;
}
查找最后一个值小于等于目标值的元素
/**
* 查找最后一个值小于等于目标值的元素
* @param array 目标数组
* @param n 目标数组大小
* @param target 目标值
* @return 目标值索引
*/
public static int binarySearchLast_1(int[] array,int n,int target){
// 低位、高位
int low = 0;
int high = n - 1;
while(low <= high){
// 计算中位数
int mid = low + ((high - low) >> 1);
if(array[mid] > target){
high = mid - 1;
}else{
// 如果mid==n-1,则最后一个元素,是查找的目标位置
// 如果min!=n-1,如果array[mid+1] > target,则mid是查找的目标位置
// 否则,继续查找
if(mid == (n - 1) ||
array[mid + 1] > target){
return mid;
}
low = mid + 1;
}
}
return -1;
}
测试代码
public static void main(String[] args) {
// 1.查找第一个值等于目标值的元素
int[] array_1 = {2,4,5,6,7,9,9,9,10};
System.out.println("目标数组:" + Arrays.toString(array_1));
int target = 8;
int index_1 = binarySearchFirst(array_1,array_1.length,target);
System.out.println("1.查找【第一个值等于】目标值:" + target+ " 的元素:" + index_1);
// 2.查找最后一个值等于目标值的元素
System.out.println("----------------华丽丽分割线------------------");
int index_2 = binarySearchLast(array_1,array_1.length,target);
System.out.println("2.查找【最后一个值等于】目标值:" + target + " 的元素:" + index_2);
// 3.查找第一个大于等于目标值的元素
System.out.println("----------------华丽丽分割线------------------");
int index_3 = binarySearchFirst_1(array_1,array_1.length,target);
System.out.println("3.查找【第一个大于等于】目标值:" + target + " 的元素:" + index_3);
// 4.查找最后一个小于等于目标值的元素
System.out.println("----------------华丽丽分割线-------------------");
int index_4 = binarySearchLast_1(array_1,array_1.length,target);
System.out.println("4.查找【最后一个值小于等于】目标值: " + target+ " 的元素:" + index_4);
}