avatar

🧊foril

avatar

🧊foril

二分查找

2023-01-21 -

写完本文后,发现灵神的讲解非常详细易懂,这里贴上视频。

原理:

二分查找的基本思想是将查找区间逐步缩小,每次排除一半的元素。具体步骤如下:

  1. 在有序数组的中间元素开始查找。
  2. 如果中间元素正好是目标值,则查找成功。
  3. 如果目标值小于中间元素,则在左侧子数组中继续查找。
  4. 如果目标值大于中间元素,则在右侧子数组中继续查找。
  5. 重复步骤1-4,直到找到目标值或查找区间为空。

Java实现

public class BinarySearch { public static int search(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { return mid; } else if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1; // 未找到目标值 } }

应用场景

二分查找适用于以下场景:

  1. 数据已排序:二分查找要求数据是有序的。如果数据未排序,需要先进行排序,然后再执行二分查找。
  2. 数据可随机访问:对于支持随机访问的数据结构(如数组),二分查找非常有效。对于不支持随机访问的数据结构(如链表),应该使用其他算法(如遍历)。

查找第一个和最后一个目标元素

查找有序数组中第一个和最后一个出现的目标元素是二分查找的一个变体。在这个问题中,数组可能包含重复的元素,而我们希望找到目标值在数组中的首次出现位置和最后一次出现位置。为了实现这个功能,我们可以分别修改二分查找的逻辑来查找第一个和最后一个出现的目标元素。

public static int findFirst(int[] arr, int target) { int left = 0; int right = arr.length - 1; int result = -1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) { result = mid; // 记录当前找到的位置 right = mid - 1; // 继续在左侧查找 } else if (arr[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return result; } public static int findLast(int[] arr, int target) { int left = 0; int right = arr.length - 1; int result = -1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) { result = mid; // 记录当前找到的位置 left = mid + 1; // 继续在右侧查找 } else if (arr[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return result; }

查找元素应该插入的位置

查找元素应该插入的位置是二分查找的另一个变体。在这个问题中,我们希望找到目标值在数组中的插入位置,以保持数组的有序性。为了实现这个功能,我们可以修改二分查找更新边界的逻辑来查找目标值应该插入的位置。

这里附上 Python bisect 模块的源码,可以看到 bisect 模块的实现也是基于二分查找的。

def bisect_left(a, x, lo=0, hi=None, *, key=None): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e < x, and all e in a[i:] have e >= x. So if x already appears in the list, a.insert(i, x) will insert just before the leftmost x already there. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. """ if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) # Note, the comparison uses "<" to match the # __lt__() logic in list.sort() and in heapq. if key is None: while lo < hi: mid = (lo + hi) // 2 if a[mid] < x: lo = mid + 1 else: hi = mid # 这里是关键,如果 a[mid] >= x,那么 hi = mid,这样就保证了最后返回的索引 i 满足 a[:i] < x, a[i:] >= x else: while lo < hi: mid = (lo + hi) // 2 if key(a[mid]) < x: lo = mid + 1 else: hi = mid return lo

通过找到应该插入的位置,bisect 包中还提供一个直接插入元素的方法 insort_left

def insort_left(a, x, lo=0, hi=None, *, key=None): """Insert item x in list a, and keep it sorted assuming a is sorted. If x is already in a, insert it to the left of the leftmost x. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. """ if key is None: lo = bisect_left(a, x, lo, hi) else: lo = bisect_left(a, key(x), lo, hi, key=key) a.insert(lo, x)