二分查找(下):如何快速定位IP对应的省份地址?
在百度,在搜索框中随机输一个IP地址,可以看到它的归属地,这是通过维护一个很大的IP地址库来实现的,在庞大的地址库中逐一对比IP地址所在的区间,非常耗时,假设我们有12W这样的IP区间与归属地的对应关系,如何快速定位出一个IP地址的归属地?
四个常见的二分查找变形问题
- 查找第一个值等于给定值的元素
- 查找最后一个值等于给定值的元素
- 查找第一个大于等于给定值的元素
- 查找最后一个小于等于给定值的元素
查找第一个值等于给定值的元素
有序数据集合中存在重复的数据,希望找到第一个值等于给定值的数据
有一个有序数组,a[5],a[6],a[7]=8,希望查找第一个等于8的数据,即下标为5的元素
public int bsearch(int[] a ,int n,int value){
int low = 0;
int high = n -1 ;
while(low <= high){
int mid = low + ((high - low)>>1);
if(a[mid] >= value){
high = mid - 1;
}else{
low = mid - 1;
}
}
if(low < n && a[low]==value) return low;
else return -1;
}
该方式不好理解,换一种实现方法:
public int bsearch(int[] a ,int n,int value){
int low = 0;
int high = n -1;
while(low <= high){
int mid = low + ((high - low) >> 1);
if(a[mid] > value){
high = mid -1;
}else if(a[mid] < value){
low = mid +1;
}else{
if(mid == 0 || (a[mid -1] != value)) return mid;
else high = mid -1;
}
}
return -1;
}
当a[mid] == value的时候该如何处理?
我们查找的是任意一个值等于给定值的元素,当a[mid] == value的时候,a[mid]就是我们要找的元素,但是还得确认一下是不是第一个值等于给定值的元素,如果mid == 0的时候那这个元素已经是数组的第一个元素,那它肯定是我们要找的,如果mid != 0,但是a[mid]的前一个元素a[mid-1] != value,说明a[mid]就是第一个值等于给定值的元素
如果经过检查之后发现a[mid-1] == value,说明此时的a[mid]不是我们要找的,更新high = mid-1,因为我们要找的肯定是在[low,mid-1]之间
变形二:查找最后一个值等于给定值的元素
public int bsearch(int[] a,int n ,int value){
int low =0;
int high = n -1 ;
while(low <= high){
int mid = low + ((high-low)>> 1 );
if(a[mid] > value){
high = mid -1;
}else if(a[mid] < value){
low = mid + 1 ;
}else{
if((mid == n-1) || (a[mid + 1] != value)) return mid;
else low = mid + 1;
}
}
return -1;
}
如果a[mid]这个元素时数组中最后一个元素,或者后一个元素a[mid + 1 ]不是value,肯定是最后一个值等于给定值的元素
变形三:查找第一个大于等于给定值的元素
查找第一个大于等于给定值的元素,如果数组中存储一个序列:3,4,6,7,10,查找第一个大于等于5的元素,即6
public int bsearch(int[] a ,int n ,int value){
int low = 0;
int high = n - 1 ;
while(low <= high){
int mid = low + ((high - low ) >>1);
if(a[mid] >= value){
if((mid == 0) || (a[mid -1] < value)) return mid;
else high = mid - 1 ;
}else{
low = mid + 1 ;
}
}
return -1;
}
变形四:查找最后一个小于等于给定值的元素
还是3,5,6,8,9,10,查找最后一个小于等于7的元素即6
public int bsearch(int[] a ,int n ,int value){
int low = 0 ;
int high = n -1;
while(low <= high){
int mid = low+((high - low) >> 1);
if(a[mid] > value){
high = mid - 1;
}else{
if((mid == n -1) || (a[mid + 1] > value)) return mid ;
else low = mid + 1;
}
}
return -1;
}
如何快速定位一个IP地址的归属地?
先预处理这12W条数据,先让其按照起始IP从小到大排序,IP地址可以转化为32位的整形数,所以我们可以将起始地址按照对应的整形数的大小关系从小到大的进行排序,然后我们可以按照变形四:查找最后一个小于等于某个给定值的元素
当我们要查询某个IP归属地的时候,可以先通过二分查找,找到最后一个起始IP小于等于这个IP的IP区间,检查这个IP是否在这个IP区间内,如果在,取出对应的归属地显示,不再,返回未查到
凡是能用二分查找解决的,更倾向于用散列表或者二叉查找树,二分查找更适合用在近似查找问题上
如果有序数组是一个循环有序数组,比如4,5,6,1,2,3,如何实现一个求“值等于给定值”的二分查找算法?
leetcode 33
一、
1.找到分界下标,分成两个有序数组
2.判断目标值在哪个有序数据范围内,做二分查找
二、
1.找到最大值的下标X
2.所有元素下标+X偏移,超过数组范围值的取模
3.利用偏移后的下标做二分查找
4.如果找到目标下标,再做-X偏移就是目标值实际下标
三:
以数组中间点为分区,会将数组分成一个有序数组和一个循环有序数组,如果首元素<mid,说明前半部分是有序的,后半部分是循环有序数组,
//从循环有序数组中查找元素,二分之后,总有一半是有序数组
public int bsearch(int[] nums ,int target){
if(nums.length == 0) return -1;
int low = 0 ;
int high = nums.length - 1;
while(low <= high){
int mid =low +((high - low) >> 1);
if(nums[mid] == target) return mid ;
if(nums[low] <= nums[mid]){
if(nums[mid] > target && nums[low] <= target){
high = mid -1;
}else{
low = mid + 1;
}
}else if (nums[low] > nums[mid]){
if (nums[mid] < target && nums[high] >= target){
low = mid + 1;
}else{
high = mid - 1;
}
}
}
return -1;
}
来源:CSDN
作者:ywangjiyl
链接:https://blog.csdn.net/ywangjiyl/article/details/103757694