二分查找:如何快速定位IP对应的省份地址?

文章来源于极客时间前google工程师−王争专栏。

通过IP地址查找IP归属地功能:
image
这个功能是通过维护一个很大的IP地址库来实现。地址库中包含IP地址范围和归属地的对应关系。

当我们查询202.201.133.13这个IP地址归属地时,在地址库中搜索,这个IP落在[202.102.133.0,202.102.133.255]这个地址范围内,就可以显示“山东东营市”给用户了。

问题是:在庞大的地址库中逐一比对IP地址所在的区间是非常耗时的。假设我们有12万条这样的IP区间与归属地的对应关系,如何快速定位出一个IP地址的归属地呢?

主要学习二分查找的变形问题。

“十个二分九个错”,二分查找虽然简单,但是想写出没有Bug的二分查找并不容易。

常见的二分查找变形问题:
image

变体一:查找第一个值等于给定值的元素

简单二分法适用于有序数据集合中不存在重复的数据,我们在其中查找值等于某个给定值的数据。

如下图所示,如果查找第一个等于8的数据?
image
上图所示,如果用简单二分法的代码实现,首先拿8与区间的中间值a[4]比较,8比6大,于是在下标5到9之间继续查找,下标5和9的中间位置是下标7,刚好等于8,所以代码就返回了。

针对这种情况,可以稍微改造下简单二分法实现。

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的情况,只需要更新high=mid-1;对于a[mid]<value的情况,只需要更新low=mid+1。当a[mid]=value的时候该如何处理呢?

如果mid=0,数组中的第一个元素,那这个肯定就是我们要找的。如果mid不等于0,a[mid]的前一个元素a[mid-1]不等于value,那么a[mid]也是我们要找的第一个给定值的元素。如果等于value,继续更新high=mid-1,因为我们要找的元素肯定在[low,mid-1]之间。

代码易读懂,没Bug,其实更重要,所以第二种写法更好。

变体二:查找最后一个值等于给定值的元素

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;
}

参照变体一

变体三:查找第一个大于等于给定值的元素

比如,数组中存储的这样一个序列: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;
}

查找最后一个小于等于给定值的元素

public int bsearch7(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地址的归属地?

首先预处理这12万条数据,让其按照起始IP从小到大排序。IP可以转化为32位的整型数。

按照起始IP从小到大排序。问题就转化为在有序数组中,查找最后一个小于等于某个给定值的元素了。

我们先通过二分查找,找到最后一个起始IP小于等于这个IP的IP区间,然后,检查这个IP是否在这个IP区间内,如果在就取对应的归属地显示;如果不在,就返回未查到。

总结

**凡是用二分查找能解决的,绝大部分我们更倾向于用散列表或者二叉查找树。**即便是二分查找在内存使用上更节省,但是内存紧缺的情况并不多。二分查找真的没什么用处了吗?

“值等于给定值”的二分查找确实不怎么会被用到,二分查找更适合用在“近似”查找问题,在这类问题上,二分查找的优势更加明显。比如这篇讲到的几种变体问题,用其他数据结构,比如散列表、二叉树,就比较难实现了。

变体二分法,容易因为细节处理不好产生Bug,这些容易出错的细节有:终止条件、区间上下界更新方法、返回值选择。

思考

循环有序数组,比如4,5,6,1,2,3。如何实现一个“值等于给定值”的二分算法呢?

对应leetcode 33题

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
全国IP地址库,十分详细 范例。 province city isp start_ip1 end_ip1 福建省 电信 1.0.1.0 1.0.3.255 广东省 电信 1.0.8.0 1.0.15.255 广东省 电信 1.0.32.0 1.0.63.255 福建省 电信 1.1.0.0 1.1.0.255 福建省 电信 1.1.2.0 1.1.3.255 福建省 电信 1.1.4.0 1.1.7.255 广东省 电信 1.1.8.0 1.1.63.255 福建省 电信 1.2.0.0 1.2.1.255 北京市 (北龙中网科技有限公司) 网通 1.2.2.0 1.2.2.255 福建省 电信 1.2.4.0 1.2.7.255 广东省 电信 1.2.8.0 1.2.127.255 广东省 电信 1.3.0.0 1.3.255.255 福建省 电信 1.4.1.0 1.4.3.255 福建省 电信 1.4.5.0 1.4.7.255 广东省 电信 1.4.8.0 1.4.127.255 广东省 电信 1.10.0.0 1.10.7.255 福建省 电信 1.10.8.0 1.10.9.255 福建省 电信 1.10.11.0 1.10.15.255 广东省 电信 1.10.16.0 1.10.127.255 北京市 方正宽带 1.12.0.0 1.15.255.255 内蒙古 联通 1.24.0.0 1.31.255.255 内蒙古 赤峰市 联通 1.24.8.0 1.24.15.255 内蒙古 乌兰察布市 联通 1.24.32.0 1.24.39.255 内蒙古 乌兰察布市 联通 1.24.80.0 1.24.87.255 内蒙古 包头市 联通 1.24.184.0 1.24.191.255 内蒙古 乌兰察布市 联通 1.25.128.0 1.25.135.255 内蒙古 锡林郭勒盟 联通 1.25.152.0 1.25.191.255 内蒙古 巴彦淖尔市 联通 1.25.228.0 1.25.255.255 内蒙古 呼伦贝尔市 联通 1.26.0.0 1.26.15.255 内蒙古 赤峰市 联通 1.26.112.0 1.26.127.255 贵州省 电信 1.48.0.0 1.49.255.255 贵州省 贵阳市(电信CDMA全省共用出口) 电信 1.49.128.0 1.49.255.255 宁夏 电信 1.50.0.0 1.50.255.255 山东省 济南市(山东大学) 教育网 1.51.192.0 1.51.255.255 黑龙江省 联通 1.56.0.0 1.63.255.255 黑龙江省 鹤岗市 联通 1.56.0.0 1.56.95.255 黑龙江省 牡丹江市 联通 1.56.128.0 1.56.255.255 黑龙江省 绥化市 联通 1.57.0.0 1.57.63.255 黑龙江省 齐齐哈尔市 联通 1.57.64.0 1.57.127.255 黑龙江省 双鸭山市 联通 1.57.128.0 1.57.191.255 黑龙江省 鸡西市 联通 1.57.192.0 1.57.255.255 黑龙江省 哈尔滨市 联通 1.58.0.0 1.58.255.255 黑龙江省 大庆市 联通 1.59.16.0 1.59.127.255 黑龙江省 佳木斯市 联通 1.59.128.0 1.59.255.255 黑龙江省 黑河市 联通 1.60.0.0 1.60.63.255 黑龙江省 七台河市 联通 1.60.64.0 1.60.95.255 黑龙江省 伊春市 联通 1.60.128.0 1.60.191.255 黑龙江省 齐齐哈尔市 联通 1.60.192.0 1.60.255.255 黑龙江省 绥化市 联通 1.61.0.0 1.61.127.255 黑龙江省 齐齐哈尔市 联通 1.61.128.0 1.61.159.255 黑龙江省 哈尔滨市 联通 1.62.0.0 1.62.127.255 黑龙江省 鸡西市 联通 1.63.0.0 1.63.31.255 黑龙江省 绥化市 联通 1.63.152.0 1.63.159.255 黑龙江省 伊春市 联通 1.63.192.0 1.63.207.255 山西省 电信 1.68.0.0 1.71.255.255 陕西省 电信 1.80.0.0 1.87.255.255 陕西省 西安市 电信 1.80.0.0 1.80.255.255 陕西省 渭南市 电信 1.81.0.0 1.81.127.255 陕西省 汉中市 电信 1.81.128.0 1.81.255.255 陕西省 商洛市 电信 1.82.0.0 1.82.31.255 陕西省 安康市 电信 1.82.32.0 1.82.63.255 陕西省 榆林市 电信 1.82.64.0 1.82.127.255 陕西省 宝鸡市 电信 1.82.128.0 1.82.163.255 陕西省 西安市 电信 1.83.0.0 1.83.255.255 陕西省 西安市 电信 1.84.64.0 1.84.127.255 陕西省 西安市 电信 1.85.0.0 1.85.23.255 陕西省 延安市 电信 1.85.64.0 1.85.95.255 陕西省 安康市 电信 1.85.96.0 1.85.135.255 陕西省 咸阳市 电信 1.85.144.0 1.85.159.255 陕西省 西安市 电信 1.85.172.0 1.85.191.255 陕西省 西安市 电信 1.85.192.0 1.86.255.255 陕西省 西安市 电信 1.87.0.0 1.87.255.255 北京市 歌华有线宽带 1.88.0.0 1.91.255.255 北京市 电信通 1.92.0.0 1.93.255.255 内蒙古 电信 1.180.0.0 1.183.255.255 内蒙古 鄂尔多斯市 电信 1.180.64.0 1.180.67.255 内蒙古 乌海市 电信 1.180.128.0 1.180.135.255 内蒙古 呼和浩特市 电信 1.182.0.0 1.182.3.255 广东省 广州市(暨南大学) 教育网 1.184.0.0 1.184.127.255 黑龙江省 联通 1.188.0.0 1.191.255.255 河南省 郑州市 电信 1.192.0.0 1.192.191.255 河南省 电信 1.192.0.0 1.199.255.255 河南省 郑州市 电信 1.193.0.0 1.193.127.255 河南省 洛阳市 电信 1.193.128.0 1.193.239.255 河南省 平顶山市 电信 1.194.0.0 1.194.63.255 河南省 开封市 电信 1.194.128.0 1.194.159.255 河南省 安阳市 电信 1.194.192.0 1.194.255.255 河南省 新乡市 电信 1.195.0.0 1.195.63.255 河南省 焦作市 电信 1.195.64.0 1.195.127.255 河南省 濮阳市 电信 1.195.128.0 1.195.147.255 河南省 三门峡市 电信 1.195.192.0 1.195.255.255 河南省 商丘市 电信 1.196.64.0 1.196.79.255 河南省 信阳市 电信 1.196.192.0 1.196.223.255 河南省 鹤壁市 电信 1.197.0.0 1.197.15.255 河南省 漯河市 电信 1.197.32.0 1.197.63.255 河南省 周口市 电信 1.197.64.0 1.197.95.255 河南省 驻马店市 电信 1.197.96.0 1.197.127.255 河南省 许昌市 电信 1.197.128.0 1.197.159.255 河南省 南阳市 电信 1.197.160.0 1.197.175.255 河南省 焦作市 电信 1.197.192.0 1.197.207.255 河南省 安阳市 电信 1.197.208.0 1.197.223.255 河南省 周口市 电信 1.197.224.0 1.197.235.255 河南省 周口市 电信 1.199.0.0 1.199.15.255 河南省 新乡市 电信 1.199.96.0 1.199.127.255 北京市 (电信WIFI热点AP网段) 电信 1.202.0.0 1.203.255.255 贵州省 贵阳市 电信 1.204.0.0 1.204.255.255

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值