目录
首先请查看本专题之前的文章关于二分查找算法的描述。
优化1:减少循环体里面执行的代码行数
/**
* 再次改动后的代码
* 优点:目的是减少循环里面的代码执行条数,原来的代码里面有 if 和 else if,现在只有一个判断条件了
* 缺点:最好的情况就不是 O(1) 了,而是 O(log(n)) 了,和最坏情况相同。
*
* @param a 递增数组
* @param target 需要查找的数据
* @return
*/
public static int binarySearch3(int[] a, int target) {
int i = 0, j = a.length;
while (1 < j - i) {
//计算中间索引
int m = (i + j) >>> 1;
//目标小于中间值,缩小有边界
if (target < a[m]) {
j = m;
} else {
//target == a[m] 或者 target > a[m],就不能够是 i = m + 1 了
i = m;
}
}
if (a[i] == target) {
return i;
} else {
return -1;
}
}
Java 中的二分查找算法
// Like public version, but without range checks.
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
思考:当数组中没有目标数据时,返回的 -(low + 1) 是什么意思?
解答:-(low + 1) 表示的是将目标数据插入到数组中时,插入的第几个位置的相反数。
例如,若 int[] a = new int[]{2, 5, 6}; target = 4,那么 返回值就是 -2,插入在第2个位置【数据 2 和 5 之间】
思考并实现案例:在一个数组中查找一个数据,若数组中不存在时,就将该数据插入到数组中去,并返回新的数组?
/**
* 实现一个功能,在一个数组中查找一个数据,若数组中不存在时,就将该数据插入到数组中去。
*/
@Test
public void test4() {
int[] a = new int[]{2, 5, 6};
int target = 4;
int i = Arrays.binarySearch(a, target);
System.out.println("i = " + i);
if (i < 0) {
//数据没有找到
//获取新的插入点
int insertIndex = Math.abs(i + 1);
//实现数组拷贝
int[] b = new int[a.length + 1];
System.arraycopy(a, 0, b, 0, insertIndex);
b[insertIndex] = target;
//拷贝 insertIndex 的后半部分
System.arraycopy(a, insertIndex, b, insertIndex + 1, a.length - insertIndex);
System.out.println(Arrays.toString(b));
}
}
//测试结果
i = -2
[2, 4, 5, 6]
Process finished with exit code 0
优化2:返回目标数据在数组数组最左侧的目标索引
提出需求:当一个数组中目标元素是重复的时,我们返回数组最左侧的目标索引,应该怎么实现呢?
/**
* 二分查找返回最左侧索引
*
* @param a 升序数组,有重复元素
* @param target 目标
* @return 返回目标在数组中的最左侧索引,不存在就返回 -1
*/
public static int binarySearchLeftmost1(int[] a, int target) {
int i = 0, j = a.length - 1;
int candidate = -1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) {
j = m - 1;
} else if (a[m] < target) {
i = m + 1;
} else {
//找到了目标,不马上返回数据,将有右指针指向 m -1,然后继续循环即可
candidate = m;
j = m - 1;
}
}
return candidate;
}
说明,和普通儿茶查找的不同点就是当数据找到了之后的处理方案不一样。
基础版本:当 target == a[m] 时,直接返回索引 m 即可。
优化版本:当 target == a[m] 时,将此时的索引 m 作为一个可选的候选值,然后将右指针指向当前候选值的前一个数据,然后重复二分查找;直到找到最后一个就一定是最左侧的索引。
优化3:返回目标数据在数组数组最右侧的目标索引
提出需求:当一个数组中目标元素是重复的时,我们返回数组最右侧的目标索引,又应该怎么实现呢?
/**
* 二分查找返回最右侧索引
*
* @param a 升序数组,有重复元素
* @param target 目标
* @return 返回目标在数组中的最右侧索引,不存在就返回 -1
*/
public static int binarySearchRightmost1(int[] a, int target) {
int i = 0, j = a.length - 1;
int candidate = -1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) {
j = m - 1;
} else if (a[m] < target) {
i = m + 1;
} else {
//找到了目标,不马上返回数据,将有左指针只想 m + 1,然后继续循环即可
candidate = m;
i = m + 1;
}
}
return candidate;
}
说明,和查找最左侧数据索引不同,当找到目标数据时,继续向右侧查找,让左指针 i 指向当前数据的下一个索引,然后继续二分循环,直到最后找到的就一定是最右侧的索引。
优化4:对返回值进行优化 【最左侧和最右侧】
在上面的优化2和优化3的代码中,当没有找到目标数据时,返回 -1 这个数据没有什么意义,观察下列代码?
/**
* 二分查找返回大于等于目标值的最左侧的索引
*
* @param a 升序数组,有重复元素
* @param target 目标
* @return 二分查找返回大于等于目标值的最左侧的索引
*/
public static int binarySearchLeftmost2(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;
}
}
//思考,返回的 i 什么意思
return i;
}
/**
* 返回小于等于 target 的最大索引
*
* @param a 升序数组,有重复元素
* @param target 目标
* @return 返回数组中 <= target 的最大索引
*/
public static int binarySearchRightmost2(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 - 1;
}
思考:
1:在 binarySearchLeftmost2() 方法中,返回的 i 表示的是什么意思呢?
2:在 binarySearchRightmost2() 方法中,返回的 i - 1 表示的什么意思呢?
解答:
1:在binarySearchLeftmost2中,i 表示在数组中,大于等于目标数据最左侧【最小】的索引。等于表示目标数据存在,大于表示目标数据不存在。
2:在 binarySearchRightmost2 中,i - 1 表示在数据中,小于等于目标数据最右侧【最大】的索引。等于表示目标数据存在,大于表示目标数据不存在。
测试:
@Test
public void test7(){
int[] a = new int[]{2, 5, 6, 6, 6, 6, 7, 9, 23};
// 二分查找8 返回大于等于目标值的最左侧的索引,应该是 9 对应的索引为 7
int i1 = BinarySearch.binarySearchLeftmost2(a, 8);
//返回大于等于6最左侧的做引,返回第一个6的索引,为 2
int i2 = BinarySearch.binarySearchLeftmost2(a, 6);
System.out.println("i1 = " + i1);
System.out.println("i2 = " + i2);
// 返回 <= target 的最靠右的索引 ,应该是元素 7,对应的索引是 6
int i3 = BinarySearch.binarySearchRightmost2(a, 8);
//返回 <= 6 的最右侧索引,返回最后一个 6,对应的索引是 5
int i4 = BinarySearch.binarySearchRightmost2(a, 6);
System.out.println("i3 = " + i3);
System.out.println("i4 = " + i4);
}
//测试结果
i1 = 7
i2 = 2
i3 = 6
i4 = 5
Process finished with exit code 0