学习笔记:二分查找算法
一、二分查找定义
二分查找(Binary Search)是一种高效的查找算法,它用于在有序数组中快速找到目标元素的位置。相比于线性查找(从头到尾一个一个查找),二分查找利用数组的排序特性,通过逐步缩小查找范围来加速查找过程。
原理
二分查找的核心思想是折半查找。在每次查找时:
- 取当前查找范围的中间元素与目标元素进行比较。
- 如果目标小于中间元素,则继续在左半部分查找;
- 如果目标大于中间元素,则在右半部分查找;
- 重复上述步骤,直到找到目标元素,或查找范围为空(即未找到)。
时间复杂度
- 时间复杂度:O(log n)
- 每次查找都将查找范围减半,因此时间复杂度为对数级别,远比线性查找 O(n) 快。
- 空间复杂度:O(1) (如果是非递归实现)
二、二分查找的条件
- 数组必须有序:无论升序还是降序,二分查找都依赖于数组的排序。
- 目标元素在有序数组中:如果数组无序,二分查找无法正常工作。
三、二分查找的步骤
- 定义查找的左右边界,即初始的查找范围。
- 计算中间位置
mid
。 - 比较
arr[mid]
与目标元素:- 如果
arr[mid]
等于目标,查找成功; - 如果
arr[mid]
大于目标,则目标应在左半部分,缩小右边界; - 如果
arr[mid]
小于目标,则目标应在右半部分,缩小左边界。
- 如果
- 重复步骤2和3,直到左右边界交错或找到目标。
四、代码实现(Java版)
非递归版
public class BinarySearch {
public static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
// 取中间位置
int mid = left + (right - left) / 2;
// 比较中间值与目标值
if (arr[mid] == target) {
return mid; // 找到目标,返回索引
} else if (arr[mid] < target) {
left = mid + 1; // 目标在右半部分
} else {
right = mid - 1; // 目标在左半部分
}
}
return -1; // 未找到目标,返回 -1
}
public static void main(String[] args) {
int[] arr = {1, 3, 5, 7, 9, 11, 13, 15}; // 已排序的数组
int target = 7;
int result = binarySearch(arr, target);
if (result != -1) {
System.out.println("目标元素 " + target + " 的索引是: " + result);
} else {
System.out.println("目标元素 " + target + " 不存在于数组中。");
}
}
}
递归版
public class BinarySearchRecursive {
public static int binarySearch(int[] arr, int target, int left, int right) {
if (left > right) {
return -1; // 查找范围为空,未找到目标
}
// 取中间位置
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid; // 找到目标
} else if (arr[mid] < target) {
return binarySearch(arr, target, mid + 1, right); // 目标在右半部分
} else {
return binarySearch(arr, target, left, mid - 1); // 目标在左半部分
}
}
public static void main(String[] args) {
int[] arr = {1, 3, 5, 7, 9, 11, 13, 15}; // 已排序的数组
int target = 7;
int result = binarySearch(arr, target, 0, arr.length - 1);
if (result != -1) {
System.out.println("目标元素 " + target + " 的索引是: " + result);
} else {
System.out.println("目标元素 " + target + " 不存在于数组中。");
}
}
}
找出一个数组中元素的起始位和末位
import java.util.Scanner;
public class BinarySearch {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[] q = new int[n];
for (int i = 0; i < n; i++) {
q[i] = scanner.nextInt();
}
while (m-- > 0) {
int x = scanner.nextInt();
int l = 0, r = n - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (q[mid] >= x) r = mid - 1;
else l = mid + 1;
}
if (l == n || q[l] != x) {
System.out.println("-1 -1");
} else {
System.out.print("1 ");
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (q[mid] <= x) left = mid + 1;
else right = mid - 1;
}
System.out.println(left);
}
}
scanner.close();
}
}
五、代码讲解
-
非递归版:
- 我们用
while (left <= right)
来控制查找范围。每次迭代时,我们通过调整left
和right
来缩小查找范围,直到找到目标元素或者查找范围为空。
- 我们用
-
递归版:
- 递归版与非递归版的逻辑一致,只是通过函数自身的递归调用来缩小查找范围。当递归的左右边界满足
left > right
时,查找结束。
- 递归版与非递归版的逻辑一致,只是通过函数自身的递归调用来缩小查找范围。当递归的左右边界满足
六、二分查找的边界条件
- 数组为空:直接返回
-1
。 - 目标不在数组中:经过多次折半查找后,左右边界交错,返回
-1
。 - 数组有重复元素:二分查找通常返回找到的第一个满足条件的元素索引,无法直接处理重复元素的情况。如果需要查找第一个或最后一个重复元素的位置,需要进行额外的处理。
七、总结
二分查找是一种高效的查找算法,特别适合在大规模有序数组中查找目标元素。它的时间复杂度为O(log n),在查找效率上远优于O(n)的线性查找。
通过掌握二分查找的基本原理和多种实现方式(非递归和递归),你可以在日常开发中处理各种查找问题,并为后续深入学习其他高级算法打下良好的基础。#