二分查找(基础版)
二分查找,即每次都先查询有序数组的中间索引位置的元素,如果该元素比目标元素大,则只需要再查找中间索引左边的元素,反之则只需要查询中间索引往右的位置。如在[1,2,3,4,5,6]中查找4这个元素,拿到中间索(0+5)/2向下取整到2,索引2的元素是3,目标元素4比3小,则我们只需要关注索引2往后的元素,即[4,5,6],一直查找到i>j的时候,说明整个数组遍历完成。
package 算法algorithm.二分算法;
/**
* @author helijian
* @program hlj
* @date 2023/6/25 9:57
* @description 在有序数组中查找目标元素,如果找到返回索引位置,找不到返回-1
*/
public class BinarySearch {
/**
* @param a
* @param target
* @return 存在的问题:①i <= j不能写成i < j,否则当i=j的时候不会比较
* ②int m = (i + j) / 2,(i+j)/2 存在问题,如果j比较大,那第二次计算(i+j)/2时,
* i+j可能会超出Integer.MAX_VALUE,导致计算出来的是一个负数(将最高位的1作为了符号位)
*/
public static int BS(int[] a, int target) {
int i = 0, j = a.length - 1;//设置两个指针,分别指向数组第一个元素和最后一个元素
while (i <= j) {
// int m = (i + j) / 2; //取中间位置
int m = (i + j) >>> 1; //无符号右移
if (target < a[m]) { //目标值在左边
j = m - 1;
} else if (a[m] < target) { //目标值在右边
i = m + 1;
} else { //目标值等于m
return m;
}
}
return -1;
}
public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5, 6, 7, 8};
int m = BS(a, 9);
System.out.println(m);
}
}
二分查找(改进版)
基础版中,查询不到返回的是-1,但是返回的-1除了告诉我们没有查询到之外,没有任何其他意义。
改进:在一个有序数组中,查询目标元素,找到返回该元素的索引,找不到则返回将该元素插入该数组时应该插入的索引位置
分析:以下数组为例,分析如果目标元素不存在,其所插入的索引用什么去表示
在这个数组中找7这个元素:target=7
- i=0,j=6,m=3 a[m]=a[3]=6<7
- i=m+1=4,j=6,m=5 a[m]=a[5]=9>7
- i=4,j=m-1=4,m=4 a[m]=a[4]=8>7
- i=4,j=m-1=3,m=3,a[m]=a[3]=6!=target
再往后, i>j了,说明数组遍历完成,循环结束,没找到,通过步骤分析,我们可以知道,如果要将7插入数组,要插入到索引4的位置,而i正好指向这个位置,所以只需要返回i即可表示待插入元素要插入的索引位置,代码如下:
public static int BS(int[] a, int target) {
int i = 0, j = a.length - 1;//设置两个指针,分别指向数组第一个元素和最后一个元素
while (i <= j) {
// int m = (i + j) / 2; //取中间位置
int m = (i + j) >>> 1; //无符号右移
if (target < a[m]) { //目标值在左边
j = m - 1;
} else if (a[m] < target) { //目标值在右边
i = m + 1;
} else { //目标值等于m
return m;
}
}
return i;
}
最左(右)元素查找
有这样一种场景,当一个有序数组的元素可以重复时,我们去查找某个元素,如果这个元素存在,希望查找到这个元素在数组中索引最小(大)的那个,如果不存在,则希望返回这个数组中 **小于等于(大于等于)**目标元素的最靠左的索引,如[1,2,3,3,5,6,6,8],如果查找3,希望找到第一个3,即索引为2的那个3,当找4的时候,4不存在,也希望找到最左边的3。
分析:
数组a=[1,2,3,3,5,6,6,8]中,找3
- i=0,j=a.length-1=7,m=3,a[m]=a[3]=3
- 找到了,但是不知道是不是最左边的3,此时继续循环
- i=0,j=m-1=2,m=1,a[m]=a[i]=2<3,说明m的左边不需要再找了,增大i的值,i=m+1
- i=m+1=2,j=2,m=2.a[m]=a[2]=3,此时a[m]=3,但还是不知道是不是左边的3,循环还没结束,继续循环
- i=2,j=m-1=1,此时i>j了,退出循环,i此时指向的就是索引2的位置,所以返回i即可
代码实现:
public static int BSLeftmost2(int[] a, int target) {
int i = 0, j = a.length - 1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target <= a[m]) {
j = m - 1;
} else {
i = m + 1;
}
}
return i;
}