赞
踩
需求:在有序数组 A 内,查找值target
前提 | 给定一个内含 n 个元素的有序数组 A,一个待查值 target |
1 | 设置 i = 0,j = n-1 |
2 | 如果 i > j,结束查找,没找到 |
3 | 设置 m = (i+j)/2 ,m 为中间索引并向下取整 |
4 | 如果 target < A[m] 设置 j = m - 1,跳到第2步 |
5 | 如果 A[m] < target 设置 i = m + 1,跳到第2步 |
6 | 如果 A[m] = target,结束查找,找到了 |
二分查找法的时间复杂度为 。
代码实现基础版
- public class search_binary {
- public static void main(String[] args) {
- int[] a = {9, 18,21, 28, 32,40,46};
- int index = search(a, 32);
- System.out.println("查找结果为:" + index);
- }
-
- public static int search(int[] a, int target) {
- int i = 0;
- int j = a.length - 1;
- while (i <= j) {
- int m = (i + j) / 2;
- if (a[m] < target) {
- //说明要查找的数在右边
- i = m + 1;
- } else if (a[m] > target) {
- j = m - 1;
- } else {
- return m;
- }
- }
- return -1;
- }
- }
由于Java中int类型会自动向下取整,因此我们不需要考虑m带小数的情况。
查找过程如下
但是它仍存在一些问题。那就是Integer数值取值问题,m有可能会变为负数,情景再现。
- public static void main(String[] args) {
- int i = 0;
- int j = Integer.MAX_VALUE;//j表示int类型所能表达的最大数
- int m = (i + j) / 2;
- System.out.println(m);
- //假设目标值在m右侧
- i = m + 1;
- m = (i + j) / 2;
- System.out.println(m);
- }
1073741823
-536870912
之所以会出现这种问题,是因为m的值超出了int所能表达的最大值。在计算机中,一个数字最后都会由二进制表示,而int类型能表达的范围是-2147483648~2147483647,最大值转化为二级制为
可以看到第一位数字为0,这是因为Java中,是需要把二进制数字中的第一位数字当作符号位的(0为正,1为负)。超过最大值的二进制表示,第一位就会变为1,在Java中自然会被识别成一个负数。
为了避免这个问题,我们可以采用右移运算,也就是将所有的二进制右移一位(相当于除以2)。
具体代码实现如下
- public static int binarySearch(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 if (a[m] < target) { // 在右边
- i = m + 1;
- } else {
- return m;
- }
- }
- return -1;
- }
代码实现改动版
- public static int binarySearch(int[] a, int target) {
- int i = 0, j = a.length;
- while (i < j) {
- int m = (i + j) >>> 1;
- if (target < a[m]) { // 在左边
- j = m;
- } else if (a[m] < target) { // 在右边
- i = m + 1;
- } else {
- return m;
- }
- }
- return -1;
- }
与基础版相比,改动版就是修改了 j 的边界值,将 j 的下次一次取值变为m。首先我们需要清楚的是,a[m]不是目标值,下标为m的数字是已经判断过了的,因此可以充当 j 的边界值。
流程如下,我们查找29的下标
但是这种实现方式存在一个问题,那就是目标值在a[m]右侧时,每次都需要先进行一次是否在a[m]左侧的操作。这就造成了每次不必要的查找,也会导致,查找左侧的元素的效率要比查找右侧的效率要高。对此,我们还可以进行优化。
具体代码如下
- public static int binarySearchBalance(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 {
- i = m;
- }
- }
- return (a[i] == target) ? i : -1;
- }
while中的判断条件为元素中剩余查找的元素个数,当剩余1个时,将退出循环,然后在外部进行判断是否是目标值。
二分查找,数组中的重复元素,其实返回的值为第一次查找到的下标,如果添加了需求,要求返回重复元素的最左侧的下标就不能满足该需求,因此,我们还需要对该二分查找进行代码补充
- public static int binarySearch(int[] a, int target) {
- int i = 0, j = a.length - 1;
- int tempDate = -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 {
- //修改返回值处的代码
- tempDate = m; // 记录当前目标值的下标
- //查看左侧是否还存在相同的目标值
- j = m -1;
- }
- }
- return -1;
- }
Java中的二分查找的实现在Arrays类中,对应代码如下
- 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.
- }
与基础版所写基本没有差别,区别在于返回值。根据文档意思,这个返回值的意思是(-插入点)-1。意思是,如果没有查找到目标值,那么就会返回目标值应该插入数组中的下标值加一并取负数。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。