vmbn465

二分查找的变形

       二分查找虽然原理极其简单,但是想要写出没有 Bug 的二分查找并不容易。之前接触的是最简单的二分查找,即在不存在重复元素的有序数组中,查找值等于给定值的元素。

变形1:查找第一个值等于给定值的元素

       比如下面这样一个有序数组,其中,a[5],a[6],a[7]的值都等于 8,是重复的数据。我们希望查找第一个等于 8 的数据,也就是下标是 5 的元素。

       如果用之前的代码,那么最后返回的是最后一个8,与要求不符。

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 {
      //如果mid=0,那这个元素已经是数组的第一个元素;如果a[mid]前一个不是,那么a[mid]就是要找的
      if ((mid == 0) || (a[mid - 1] != value)) return mid;
      else high = mid - 1;
    }
  }
  return -1;
}

变形2:查找最后一个值等于给定值的元素

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;
}

变形3:查找第一个大于等于给定值的元素

       现在我们再来看另外一类变形问题。在有序数组中,查找第一个大于等于给定值的元素。比如,数组中存储的这样一个序列:3,4,6,7,10。如果查找第一个大于等于 5 的元素,那就是 6。

       实际上,实现的思路跟前面的那两种变形问题的实现思路类似,代码写起来甚至更简洁。

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;
}

变形4:查找最后一个小于等于给定值的元素

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;
}

小结

       凡是用二分查找能解决的,绝大部分我们更倾向于用散列表或者二叉查找树。即便是二分查找在内存使用上更节省,但是毕竟内存如此紧缺的情况并不多。

       二分查找更适合用在“近似”查找问题,在这类问题上,二分查找的优势更加明显。

       变体的二分查找算法写起来非常烧脑,很容易因为细节处理不好而产生 Bug,这些容易出错的细节有:终止条件、区间上下界更新方法、返回值选择。

分类:

技术点:

相关文章: