Skip to content

二分查找 #13

@yifanzheng

Description

@yifanzheng

二分查找

二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0。其时间复杂度是 O(logn),所以是一直非常高效的查找算法。

不过,并不是什么情况下都可以用二分查找,它的应用场景是有很大局限性的,如下:

  • 二分查找只能用在数据是通过顺序表来存储的数据结构上(数组):如果你的数据是通过其他数据结构存储的,则无法应用二分查找。
  • 二分查找针对的是有序数据:如果数据没有序,我们需要先排序。但是针对存在频繁的插入和删除操作数据集合,维护有序的成本是很高的。
  • 数据量太小不适合二分查找:如果要处理的数据量很小,完全没有必要用二分查找,顺序遍历就足够了。只有数据量比较大的时候,二分查找的优势才会比较明显。不过,如果数据之间的比较操作非常耗时,不管数据量大小,都推荐使用二分查找。比如,数组中存储的都是长度超过 300 的字符串,如此长的两个字符串之间比对大小,就会非常耗时。我们需要尽可能地减少比较次数,而比较次数的减少会大大提高性能,这个时候二分查找就比顺序遍历更有优势。
  • 数据量太大也不适合二分查找:二分查找的底层需要依赖数组这种数据结构,而数组为了支持随机访问的特性,要求内存空间连续,对内存的要求比较苛刻。比如,我们有 1GB 大小的数据,如果希望用数组来存储,那就需要 1GB 的连续内存空间。

以下是二分查找的变形算法。

简单二分查找

适用于没有重复元素的有序数组。

public int binarySearch(int[] arr, int target) {
      if (arr == null || arr.length == 0) {
          return -1;
      }
      int low = 0;
      int high = arr.length - 1;
      while (low <= high) {
          int mid = low + (high - low) / 2;
          if (arr[mid] == target) {
              return mid;
          }
          if (arr[mid] < target) {
              low = mid + 1;
          } else {
              high = mid - 1;
          }
      }
      return -1;
}

变体一:查找第一个等于目标元素的位置

适用于存在重复元素的有序数组,并且是找到第一个值等于目标元素的情况。

public int binarySearch(int[] arr, int target) {
    if (arr == null || arr.length == 0) {
        return -1;
    }
    int low = 0;
    int high = arr.length - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (arr[mid] > target) {
            high = mid - 1;
        } else if (arr[mid] < target) {
            low = mid + 1;
        } else { // arr[mid] == target
            // 判断目标元素是否是第一个出现的
            if (mid == 0 || arr[mid - 1] != target) {
                return mid;
            }
            high = mid - 1;
        }
    }

    return -1;
}

变体二:查找最后一个等于目标元素的位置

适用于存在重复元素的有序数组,并且是找到最后一个值等于目标元素的情况。

public int binarySearch(int[] arr, int target) {
    if (arr == null || arr.length == 0) {
        return -1;
    }
    int low = 0;
    int high = arr.length - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (arr[mid] > target) {
            high = mid - 1;
        } else if (arr[mid] < target) {
            low = mid + 1;
        } else { // arr[mid] == target
            // 判断目标元素是否是第一个出现的
            if ((mid == arr.length - 1) || arr[mid + 1] != target) {
                return mid;
            }
            low = mid + 1;
        }
    }
    return -1;
}

变体三:查找第一个大于等于目标元素的位置

public int binarySearch(int[] arr, int target) {
  if (arr == null || arr.length == 0) {
      return -1;
  }
  int low = 0;
  int high = arr.length - 1;
  while (low <= high) {
      int mid = low + (high - low) / 2;
      if (arr[mid] >= target) {
          if (mid == 0 || arr[mid - 1] < target) {
              return mid;
          }
          high = mid - 1;
      } else if (arr[mid] < target) {
          low = mid + 1;
      }
  }
  return -1;
}

变体四:查找最后一个小于等于目标元素的位置

public int binarySearch(int[] arr, int target) {
    if (arr == null || arr.length == 0) {
        return -1;
    }
    int low = 0;
    int high = arr.length - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (arr[mid] > target) {
            high = mid - 1;
        } else if (arr[mid] <= target) {
            if ((mid == arr.length -1) || arr[mid + 1] > target) {
                return mid;
            }
            low = mid + 1;
        }
    }
    return -1;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions