数据结构与算法
一、二分查找
前言
提示:以下是本篇文章正文内容,下面案例可供参考
一、二分查找是什么?
简介:
二分查找,也称为折半查找,是一种高效的搜索算法,主要用于在有序数组中查找特定元素的位置。这种算法的基本思想是不断将查找范围缩小为一半,直到找到目标元素或确定目标元素不存在。
具体步骤:
1、首先,确定查找范围的上下界,通常是整个数组。
2、计算中间元素的索引,检查中间元素是否等于目标值。
3、如果中间元素等于目标值,则查找成功,返回索引。
4、如果中间元素大于目标值,说明目标值可能在左半部分,缩小查找范围为左半部分。
5、如果中间元素小于目标值,说明目标值可能在右半部分,缩小查找范围为右半部分。
6、重复上述步骤,直到找到目标元素或查找范围为空。
主要特点:
- 二分查找要求数组是有序的,这是因为它需要通过比较来决定缩小查找范围的方向。
- 时间复杂度为O(log n),其中n是数组的长度。这是因为每次比较都将查找范围缩小为一半,所以查找的时间复杂度是对数级别的。
- 二分查找是一种分治策略,将问题拆分为更小的子问题来解决。
二、代码实现
1.基础版
public static int binarySearch(int[] a,int target){
int i = 0, j = a.length - 1;//这里是左闭右闭的写法,比较常用
while(i <= j){
int m = (i + j) >>> 1;
if(target < a[m]){
j = m - 1; //在左边的情况
}else if (a[m] < target){
i = m + 1; //在右边的情况
}else {
return m; //找到了
}
}
return -1; //没找到
}
2.Java源码解析
代码如下(示例):
/**
* Searches the specified array of ints for the specified value using the
* binary search algorithm. The array must be sorted (as
* by the {@link #sort(int[])} method) prior to making this call. If it
* is not sorted, the results are undefined. If the array contains
* multiple elements with the specified value, there is no guarantee which
* one will be found.
*
* @param a the array to be searched
* @param key the value to be searched for
* @return index of the search key, if it is contained in the array;
* otherwise, <code>(-(<i>insertion point</i>) - 1)</code>. The
* <i>insertion point</i> is defined as the point at which the
* key would be inserted into the array: the index of the first
* element greater than the key, or {@code a.length} if all
* elements in the array are less than the specified key. Note
* that this guarantees that the return value will be >= 0 if
* and only if the key is found.
*/
public static int binarySearch(int[] a, int key) {
return binarySearch0(a, 0, a.length, key); //这里调用了下面代码块的函数
}
// Like public version, but without range checks.
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;// 记录mid为left和right的和的一半
int midVal = a[mid];// 中间值
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // 找到了就返回mid
}
return -(low + 1); // 没找到的情况
}
注意:
这里如果没找到返回了一个值:-(low + 1)
,通过这个返回值我们可以得到最后指针停留的位置,我们可以找到我们查找的这个值应该放在数组的哪个位置。至于这里为什么没有返回 low
,原因是这样的:如果返回 low
的话就会出现一个问题,当返回值是零的时候,我们无法分辨这个值是成功找到了元素,他的下标为零;还是没找到元素,他应该放的位置的下标为零。
遇见重复元素的处理
如果我们遇见了连续的重复元素,我们想要控制自己输出的元素是第一个重复的元素,我们应该如何处理呢?
//最靠左的(LeftMost)
public static int binarySearch(int[] a,int target){
int i = 0, j = a.length - 1;
int candidate = -1;//定义一个 candidate来保存查找的值的下标,即侯选位置,如果没有找到会返回 -1
while(i <= j){
int m = (i + j) >>> 1;
if(target < a[m]){
j = m - 1;
}else if (a[m] < target){
i = m + 1;
}else {
//记录候选的位置
candidate = m;
j = m - 1;//向左找
}
}
return candidate;
}
//最靠右的(RightMost)
public static int binarySearch(int[] a,int target){
int i = 0, j = a.length - 1;
int candidate = -1;//记录侯选位置,如果没有找到会返回 -1
while(i <= j){
int m = (i + j) >>> 1;
if(target < a[m]){
j = m - 1;
}else if (a[m] < target){
i = m + 1;
}else {
//记录候选的位置
candidate = m;
i = m + 1;//向右找
}
}
return candidate;
}
/* 代码进化版
最靠左的(LeftMost)
Params:a - 待查找的升序数组
target - 待查找的目标值
Returns:
返回大于等于 target 的最靠左的索引
*/
public static int binarySearch(int[] a,int target){
int i = 0, j = a.length - 1;
while(i <= j){
int m = (i + j) >>> 1;
if(target <= a[m]){
j = m - 1;
}else {
i = m + 1;
}
return i;//返回一个更有用的值,如果有这个目标值会返回最靠左边的目标值的下标,如果没有这个目标值会返回比目标值大的最靠左的值的下标
}
/* 代码进化版
最靠右的(RightMost)
Params:a - 待查找的升序数组
target - 待查找的目标值
Returns:
返回小于等于 target 的最靠右索引
*/
public static int binarySearch(int[] a,int target){
int i = 0, j = a.length - 1;
while(i <= j){
int m = (i + j) >>> 1;
if(target < a[m]){
j = m - 1;
}else {
i = m + 1;
}
return i - 1;//返回一个更有用的值,如果有这个目标值会返回最靠右边的目标值的下标,如果没有这个目标值会返回比目标值小的最靠右的值的下标
}
使用案例
在说一些具体的使用案例之前我们先了解一下几个名词:
前任:你查找的位置的前面一个位置;
后任:你查找的位置的后面一个位置;
最近邻居:前任和后任离你更近的一个。
具体使用情景:
1、求排名:在一个班级中需要根据分数来排名,如果你想要知道自己的排名,就可以使用二分查找,你的排名就是 LeftMost + 1
;
2、求前任:前任的下标就是 LeftMost - 1
;
3、求后任:后任的下标就是 RightMost + 1
;
4、求数组中满足某些条件的数的集合:比如说数组中满足x < 4
的值的集合,我们就可以通过二分查找来返回LeftMost(4)
,然后这个集合就是下标0 到 LeftMost(4) - 1
,以此类推,可以求满足某个条件的数的集合。
一些练习题
这里给大家推荐一些练习题: LeetCode 704.二分查找;
这个题目是经典的二分查找题目,来自刷题网站力扣,大家没事可以多看看力扣上面的每日一题,没事可以做一做,有的题目还是非常有意思的。
如果大家对这个题目有什么疑问的话可以看看下面这篇文章:
链接: LeetCode 704.二分查找题目解析。
大家通过这个题目的练习应该就可以使用二分查找思想来写题目了,如果大家还有兴趣也可以写一下下面两个题目:
LeetCode 35.搜索插入位置;
LeetCode 34.在排序数组中查找元素的第一个和最后一个位置。
总结
本篇文章为大家具体讲解了二分查找算法具体的含义,以及不同的写法孰优孰劣,分析了Java 源码中的二分查找算法,以及写了优化后的二分查找代码,为大家分析了在什么情况下可以使用二分查找思想,并且为大家找了一些练习题来供大家练习。那么这篇文章的干货内容就到此结束了,如果文章有什么问题或者大家有什么好的建议的话可以提出来博主看见都会回复,欢迎大家和博主互相交流。