二分查找
二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0。
二分查找引用场景的局限性:
1、二分查找依赖的是顺序表结构,简单点说就是数组。
2、二分查找针对的是有序数据。
3、数据量太小不适合二分查找。如果要处理的数据量很小,完全没有必要用二分查找,顺序遍历就足够了。不过,这里有一个例外。如果数据之间的比较操作非常耗时,不管数据量大小,我都推荐使用二分查找,比如字符串的比较,这种情况下二分查找能尽可能地减少比较次数。
4、数据量太大也不适合二分查找。其实这条是基于第一条的,因为数据量太大意味着开辟了一块很大的连续内存空间,这在数据的存储上显然已经是不合理的了。
通过IP地址来查找IP归属地的功能:是通过维护一个很大的IP地址库来实现的。地址库中包括IP地址范围和归属地的对应关系。
常规:查找值值等于给定值的元素
static int binarySearch(int[] arr, int value) {
int len=arr.length;
int left=0,right=len-1;
while(left<=right){
int mid=left+((right-left)>>1);
if(arr[mid]<value){
left=mid+1;
}else if(arr[mid]>value){
right=mid-1;
}else{
return mid;
}
}
return -1;
}
变体一:查找第一个值等于给定值的元素
即查找到当前元素值等于目标值后,判断当前位置前面是否还有同样等于目标值的元素,有的话就说明还不是第一个,即right=mid-1
static int binarySearch(int[] arr, int value) {
int len=arr.length;
int left=0,right=len-1;
while(left<=right){
int mid=left+((right-left)>>1);
if(arr[mid]<value){
left=mid+1;
}else if(arr[mid]>value){
right=mid-1;
}else{
if(mid==0||arr[mid-1]!=value){
return mid;
}else{
right=mid-1;
}
}
}
return -1;
}
变体二:查找最后一个值等于给定值的元素
就是变体一小改一下
static int binarySearch(int[] arr, int value) {
int len=arr.length;
int left=0,right=len-1;
while(left<=right){
int mid=left+((right-left)>>1);
if(arr[mid]<value){
left=mid+1;
}else if(arr[mid]>value){
right=mid-1;
}else{
if(mid==len-1||arr[mid+1]!=value){
return mid;
}else{
left=mid+1;
}
}
}
return -1;
}
变体三:查找第一个大于等于给定值的元素
static int binarySearch(int[] arr, int value) {
int len=arr.length;
int left=0,right=len-1;
while(left<=right){
int mid=left+((right-left)>>1);
if(arr[mid]<value){
left=mid+1;
}else if(arr[mid]>=value){
if(mid==0||arr[mid-1]<value){
return mid;
}else{
right=mid-1;
}
}
}
return -1;
}
变体四:查找最后一个小于等于给定值的元素
懒得写了,就是变体三改下边界条件和大于小于号方向🤗
凡是用二分查找能解决的,绝大部分我们更倾向于用散列表或者二叉查找树。即便是二分查找在内存使用上更节省,但是毕竟内存如此紧缺的情况并不多。那二分查找真的没什么用处了吗?
实际上,上一节讲的求“值等于给定值”的二分查找确实不怎么会被用到,二分查找更适合用在“近似”查找问题,在这类问题上,二分查找的优势更加明显。比如今天讲的这几种变体问题,用其他数据结构,比如散列表、二叉树,就比较难实现了。
课后思考题:
对应力扣33
搜索旋转排序数组
class Solution {
public int search(int[] nums, int target) {
int len=nums.length;
int left=0,right=len-1;
while(left<=right){
int mid=left+((right-left)>>1);
if(nums[mid]==target){return mid;}
if(nums[left]<=nums[mid]){
//左半边有序
if(nums[left]<=target&&target<nums[mid]){
right=mid-1;
}else{
left=mid+1;
}
}else{
//右半边有序
if(nums[mid]<target&&target<=nums[right]){
left=mid+1;
}else{
right=mid-1;
}
}
}
return -1;
}
}