1.二分查找算法概述
二分查找算法,也称为折半查找,是一种高效的查找算法。它在一个已经排序的数组中查找目标值,时间复杂度为 O(logn)。
2.二分查找法思维步骤
- 初始边界:定义左边界 (
left
) 和右边界 (right
)。 - 取中间值:计算中间值索引
mid = (left + right) / 2
。 - 比较目标值与中间值:
- 如果目标值等于中间值,查找成功,返回索引。
- 如果目标值小于中间值,继续在左半部分搜索,调整右边界为
mid - 1
。 - 如果目标值大于中间值,继续在右半部分搜索,调整左边界为
mid + 1
。
- 查找结束:当左边界超过右边界时,说明目标值不在数组中,查找失败,返回
-1
。
3. JAVA代码实现
public class BinarySearch {
// 二分查找算法实现
public static int binarySearch(int[] array, int target) {
int left = 0; // 左边界
int right = array.length - 1; // 右边界
// 当左边界小于等于右边界时继续查找
while (left <= right) {
// 取中间索引,防止 (left + right) 可能溢出
int mid = left + (right - left) / 2;
// 如果中间值等于目标值,返回索引
if (array[mid] == target) {
return mid;
}
// 如果目标值小于中间值,则在左半部分继续查找
if (array[mid] > target) {
right = mid - 1;
} else { // 如果目标值大于中间值,则在右半部分继续查找
left = mid + 1;
}
}
// 如果找不到目标值,返回 -1
return -1;
}
public static void main(String[] args) {
// 测试数据
int[] array = {1, 3, 5, 7, 9, 11, 13, 15};
int target = 7;
// 调用二分查找
int result = binarySearch(array, target);
// 输出结果
if (result != -1) {
System.out.println("目标值 " + target + " 在数组中的索引为: " + result);
} else {
System.out.println("目标值 " + target + " 未在数组中找到。");
}
}
}
这里面不知道有没有人注意到,我定义中间索引的时候使用了mid = left + (right - left) / 2
其实,这样做的目的是为了计算出来的mid防止溢出,因为mid定义的数据类型是int他的最大值也就只有2的32次方-1,所以这里要注意下。
4.复杂度
- 时间复杂度:二分查找的时间复杂度为 O(logn)O(\log n)O(logn),因为每次查找都将搜索范围缩小一半。
- 空间复杂度:二分查找的空间复杂度为 O(1)O(1)O(1),因为只需使用常量空间存储边界和中间值。
5.二分查找的优缺点
- 优点:
- 查找速度快,适合大规模数据的查找。
- 实现简单,代码易于理解和维护。
- 只需要常数空间,节省内存。
- 缺点:
- 要求数据有序:如果数据没有排序,需要先进行排序,排序的复杂度为 O(nlogn),这会增加开销。
- 不适用于链表等不支持随机访问的结构,适合数组或随机访问时间复杂度为 O(1) 的数据结构。
6.变种算法
- 查找第一个等于给定值的元素:当数组中有多个目标值时,二分查找可以调整为找到第一个目标值。
- 查找最后一个等于给定值的元素:与查找第一个类似,只需在更新边界时做相应调整。
- 查找第一个大于等于目标值的元素:用于查找不小于目标值的最小元素。
- 查找最后一个小于等于目标值的元素:查找不大于目标值的最大元素。
6.1 查找第一个等于给定值的元素
public static int findFirstEqual(int[] array, int target) {
int left = 0, right = array.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (array[mid] == target) {
if (mid == 0 || array[mid - 1] != target) {
return mid; // 第一个等于目标值
} else {
right = mid - 1; // 继续在左边查找
}
} else if (array[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 没有找到
}
这里改动很简单,因为数组是有序的,就是当我们第一次找出目标值的时候,然后让right-1,紧接着接着在左侧查找,然后要判断一种直到mid到最左侧边界还是等于目标值的特殊情况,二是往第一次查找到的目标值的左侧去挨个判断,为什么是这样判断,还是因为它是有序数组,和由多个目标值的时候,一定是连续的,像这样1,2,3,4,4,4,4,5,6,7,8,9,9,10不能是1,2,3,4,5,4,5,4,6,7这样就是无序的了。
6.2 查找最后一个等于给定值的元素
public static int findLastEqual(int[] array, int target) {
int left = 0, right = array.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (array[mid] == target) {
if (mid == array.length - 1 || array[mid + 1] != target) {
return mid; // 最后一个等于目标值
} else {
left = mid + 1; // 继续在右边查找
}
} else if (array[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 没有找到
}
这和第一种情况一样,只需要改动边界值就好
6.3 查找第一个大于等于目标值的元素
public static int findFirstGreaterEqual(int[] array, int target) {
int left = 0, right = array.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (array[mid] >= target) {
if (mid == 0 || array[mid - 1] < target) {
return mid; // 第一个大于等于目标值的元素
} else {
right = mid - 1; // 继续在左边查找
}
} else {
left = mid + 1;
}
}
return -1; // 没有找到
}
这里的思想就是在一个有序的数组中,先通过mid折中随便找到一个只要大于等于目标值的值,然后在向这个值的左侧去挨个比较就好,直到小于目标值,其实这里最坏情况就很坏了,时间复杂度会变成O(n/2),例如,我的目标恰巧是数组第一个值,那第一次找到的中间值,是(n-1)/2,那样就会从中间值的位置挨个向左比较。
6.4 查找最后一个小于等于目标值的元素
public static int findLastLessEqual(int[] array, int target) {
int left = 0, right = array.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (array[mid] <= target) {
if (mid == array.length - 1 || array[mid + 1] > target) {
return mid; // 最后一个小于等于目标值的元素
} else {
left = mid + 1; // 继续在右边查找
}
} else {
right = mid - 1;
}
}
return -1; // 没有找到
}
这里和6.3的实例一样的思维,只要改变边界值就好。
6.5 未查找到,就按顺序插入
public class BinarySearch {
// 二分查找算法实现
public static int binarySearch(int[] array, int target) {
int left = 0; // 左边界
int right = array.length - 1; // 右边界
// 当左边界小于等于右边界时继续查找
while (left <= right) {
// 取中间索引,防止 (left + right) 可能溢出
int mid = left + (right - left) / 2;
// 如果中间值等于目标值,返回索引
if (array[mid] == target) {
return mid;
}
// 如果目标值小于中间值,则在左半部分继续查找
if (array[mid] > target) {
right = mid - 1;
} else { // 如果目标值大于中间值,则在右半部分继续查找
left = mid + 1;
}
}
// 如果找不到目标值,返回插入点
return right + 1;
}
//插入函数
这里其实就是利用了,right和left的含义,如果没有找到目标值,left就是返回的插入点,而right需要+1才能使插入点。