Skip to content

二分查找及其变种

二分查找

  • 原理:针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0
  • 时间复杂度为O(logn)
    • 计算方式, 如下图, 最坏的情况下n/2k=1时, 找到元素,求得 k=log2n 1744804637313
  • 注意:mid=(low+high)/2可能导致溢出,可以写成low+(high-low)/2, 更进一步可写成low+((high-low)>>1)
Java
publicclassBinarySearch {
    /**
     * 二分查找方法
     *
     * @param arr    已排序的数组
     * @param target 目标值
     * @return目标值在数组中的索引,如果未找到则返回 -1
     */
publicstaticintbinarySearch(int[] arr, inttarget) {
        intleft = 0; // 左边界
intright = arr.length - 1; // 右边界

while(left <= right) {
            intmid = left + (right - left) / 2; // 计算中间位置

if(arr[mid] == target) {
                returnmid; // 找到目标值,返回索引
} elseif(arr[mid] < target) {
                left = mid + 1; // 目标值在右半部分,调整左边界
} else{
                right = mid - 1; // 目标值在左半部分,调整右边界
}
        }

        return-1; // 未找到目标值
}

    publicstaticvoidmain(String[] args) {
        int[] arr = {1, 3, 5, 7, 9, 11, 13, 15}; // 已排序的数组
inttarget = 7; // 目标值

intresult = binarySearch(arr, target);

        if(result != -1) {
            System.out.println("目标值 " + target + " 在数组中的索引为: " + result);
        } else{
            System.out.println("目标值 " + target + " 未找到");
        }
    }
}

变种

1744804648141

  • 查找第一个值等于给定值的元素
Java
publicclassBinarySearchFirstOccurrence {

    /**
     * 查找第一个值等于给定值的元素
     *
     * @param arr    已排序的数组
     * @param target 目标值
     * @return第一个等于目标值的元素的索引,如果未找到则返回 -1
     */
publicstaticintbinarySearchFirstOccurrence(int[] arr, inttarget) {
        intleft = 0; // 左边界
intright = arr.length - 1; // 右边界

while(left <= right) {
            intmid = left + (right - left) / 2; // 计算中间位置

if(arr[mid] < target) {
                left = mid + 1; // 目标值在右半部分,调整左边界
} elseif(arr[mid] > target) {
                right = mid - 1; // 目标值在左半部分,调整右边界
} else{
                // 找到目标值,检查是否是第一个
if(mid == 0 || arr[mid - 1] != target) {
                    returnmid; // 是第一个匹配的元素
} else{
                    right = mid - 1; // 继续向左搜索
}
            }
        }

        return-1; // 未找到目标值
}

    publicstaticvoidmain(String[] args) {
        int[] arr = {1, 3, 5, 5, 5, 7, 9, 11, 13, 15}; // 已排序的数组
inttarget = 5; // 目标值

intresult = binarySearchFirstOccurrence(arr, target);

        if(result != -1) {
            System.out.println("第一个值等于 " + target + " 的元素索引为: " + result);
        } else{
            System.out.println("目标值 " + target + " 未找到");
        }
    }
}
  • 查找最后一个值等于给定值的元素
Java
public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] != value)) return mid;
      else low = mid + 1;
    }
  }
  return -1;
}
  • 查找第一个值大于等于给定值的元素
Java
public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] >= value) {
      if ((mid == 0) || (a[mid - 1] < value)) return mid;
      else high = mid - 1;
    } else {
      low = mid + 1;
    }
  }
  return -1;
}
  • 查找最后一个值小于等于给定值的元素
Java
public int bsearch7(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] > value)) return mid;
      else low = mid + 1;
    }
  }
  return -1;
}

案例: 如何快速定位出一个 IP 地址的归属地?

如果 IP 区间与归属地的对应关系不经常更新,我们可以先预处理这 12 万条数据,让其按照起始 IP 从小到大排序。如何来排序呢?我们知道,IP 地址可以转化为 32 位的整型数。所以,我们可以将起始地址,按照对应的整型值的大小关系,从小到大进行排序。

然后,这个问题就可以转化为我刚讲的第四种变形问题“在有序数组中,查找最后一个小于等于某个给定值的元素”了。

当我们要查询某个 IP 归属地时,我们可以先通过二分查找,找到最后一个起始 IP 小于等于这个 IP 的 IP 区间,然后,检查这个 IP 是否在这个 IP 区间内,如果在,我们就取出对应的归属地显示;如果不在,就返回未查找到。