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

news2024/10/7 20:30:13

文章来源于极客时间前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题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1121248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

真实感受:是智能家居在选择合适的技术!

科技从来都是为了让我们的生活更加的简单、舒适&#xff0c;而智能家居的智能&#xff0c;体现在如何更更更方便的使用我需要控制的家居。 例如&#xff1a;下班躺在床上想休息&#xff0c;房间和大厅的灯还开着&#xff0c;这时你会选择什么产品躺着解决问题&#xff1f; 红外…

【MySQL】逻辑架构

逻辑架构 逻辑架构剖析服务器处理客户端请求连接层服务层SQL Interface : SQL接口Parser : 解析器Optimizer : 查询优化器Caches&Buffers : 查询缓存组件 引擎层存储层 SQL执行流程MySQL查询流程查询缓存解析器词法分析语法分析 优化器执行器 数据库缓冲池 逻辑架构剖析 服…

云安全—责任共担

0x00 前言 云安全的职责范围实际上一直遵循的是&#xff0c;谁提供谁负责&#xff0c;如果交付给云消费者的时候&#xff0c;交付者使用过程中就要自行负责&#xff0c;也就是我们经常遇到的配置不当等问题&#xff0c;在三层服务模式中&#xff0c;责任互相嵌套&#xff0c;最…

软件测试(五)自动化 selenium

文章目录 自动化测试单元测试&#xff1a;单元测试&#xff1a;UI自动化 selenium工具定义特点&#xff1a;原理&#xff1a;seleniumjava环境搭建SeleniumAPI获取测试结果&#xff1a;添加等待浏览器操作键盘事件鼠标事件多层框架/窗口定位下拉框处理弹窗处理上传文件操作关闭…

10种常用基础模块电路,电子控制不再是难题!

你是否曾经为电子控制中的复杂电路而烦恼&#xff1f; 现在&#xff0c;我将向你展示10个最具实用性和普遍性的模块电路图&#xff0c;让你轻松掌握电子控制的核心技术&#xff01; 这些电路图不仅简单易懂&#xff0c;而且非常具有趣味性&#xff1a; 1、RS232通讯电路&…

Java面试——RPC协议

涉及到分布式方面知识的话&#xff0c;RPC协议是逃不开的&#xff0c;所以在此记录一下RPC协议。 什么是RPC协议 RPC协议&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;简单的来说&#xff1a;RPC协议是一种通过网络从远程计算机程序获取服务的协议…

【unity小技巧】实现无限滚动视图和类似CSGO的开箱抽奖功能及Content Size Fitter组件的使用介绍

文章目录 一篇一句前言素材一、无限滚动视图1. 绘制视图2. Content Size Fitter是布局控件&#xff08;1&#xff09;在文本框中使用&#xff08;2&#xff09;控制Scroll View(Scroll Rect组件)控件下Content的大小 3. 控制视图无限滚动4. 向右拉无限滚动5. 修复滚动视图一卡一…

julia笔记:字符和字符串

1 字符 Char类型的值代表单个字符 ca #a: ASCII/Unicode U0061 (category Ll: Letter, lowercase)typeof(c) #Char 将 Char 转换为其对应的整数值&#xff0c;即 Unicode 代码 cInt(c) c #97typeof(c) #Int64 将一个整数值&#xff08;Unicaode&#xff09;转回 Char Cha…

杂谈:DC对Verilog和SystemVerilog语言的支持

DC对Verilog和SystemVerilog语言的支持 设计语言用哪种&#xff1f;Design Compiler对二者的支持简单的fsm电路测试测试结果对比写在最后 设计语言用哪种&#xff1f; 直接抛出结论&#xff1a;先有电路&#xff0c;后为描述。设计端而言&#xff0c;没有语言的高低好坏&#…

IMX6ULL板开发——第一个应用程序

实现第一个应用程序&#xff1a;在IMX6ULL开发板上运行程序hello.c #include <stdio.h>/* 执行命令: ./hello weidongshan* argc 2* argv[0] ./hello* argv[1] weidongshan*/int main(int argc, char **argv) {if (argc > 2)printf("Hello, %s!\n", arg…

ASRPRO语音识别模块

ASRPRO语音识别模块 SOFT IIC 与PCA9685模块通信 pca9685 iic通信 地址位 ADDR<<1|0 左移一位 #define I2C_WRITE 0 #define I2C_READ 1 否则通信地址错误 asrpro 通过UART与电脑连接&#xff0c;可以进行简单的交互 将STM32作为接口扩展&#xff0c;通过SPI或I…

【RNA folding】RNA折叠算法与生物物理约束

文章目录 RNA折叠RNA folding representation1 DP for simple folds1.1 Nussinov Algorithm objective1.2 energy constraints1.3 The key idea of the algorithm 2 DP for stacking and complex foldsStochastic context free grammars 来自Manolis Kellis教授&#xff08;MIT…

JavaSE入门---掌握抽象类和接口

文章目录 抽象类什么是抽象类&#xff1f;抽象类语法抽象类特性 接口什么是接口&#xff1f;接口语法接口使用接口特性实现多个接口接口间的继承 抽象类 VS 接口Java中的 Clonable 接口Java中的 Object 类 抽象类 什么是抽象类&#xff1f; 在面向对象的概念中&#xff0c;所…

Linux编译器-gcc/g++使用函数库

【Linux】系列文章目录 【Linux】基础常见指令&#xff1a;http://t.csdn.cn/hwLPb 【Linux】基本权限&#xff1a;http://t.csdn.cn/faFZg 【Linux】软件包管理器yum与环境开发工具vim&#xff1a;http://t.csdn.cn/LEqkm 目录 【Linux】系列文章目录 前言 一、gcc选项总…

【C语言】malloc()函数详解(动态内存开辟函数)

一.malloc()函数简介 我们先来看一下cplusplus.com - The C Resources Network网站上malloc()函数的基本信息&#xff1a; 1.函数功能 可以看到,malloc()函数的功能是:向内存申请一块连续可用的空间,并返回指向块开头的指针. 2.函数参数 该函数有1个参数,是: void* malloc (…

关于iterm2的美化

iterm2 美化 笔者公司最近给发了一个新 M1 mac pro&#xff0c;所以一些软件需要重新安装。其中比较麻烦就是iterm2的一个美化工程 &#xff0c; 由于每次安装的效果都不尽相同所以这次写一个博客来记录一下 安装的过程 。 全程高能开始&#xff1a; 使用brew 来安装 iterm2 …

26. 通过 cilium pwru了解网络包的来龙去脉

pwru是一种基于eBPF的工具,可跟踪Linux内核中的网络数据包,并具有先进的过滤功能。它允许对内核状态进行细粒度检查,以便通过调试网络连接问题来解决传统工具(如iptables TRACE或tcpdump)难以解决甚至无法解决的问题。在本文中,我将介绍pwru如何在不必事先了解所有内容的…

C++ 模板类编译前需要实例化

在复习list容器的模拟实现&#xff0c;遇到了模板未实例化导致代码报错的问题&#xff0c;list的模拟实现可以看下面这篇文章&#xff0c;这里就不多赘述了&#xff0c;直接切入正题。Clist模拟http://t.csdnimg.cn/N6BGh现在的情况是我们已经模拟实现了list类&#xff0c;想在…

1024 漏洞综合项目演示(第十七课)

1024 漏洞综合项目演示(第十七课) 一 渗透流程 前期交互&#xff08;Reconnaissance&#xff09;&#xff1a;这是一个信息收集的阶段&#xff0c;黑客或攻击者会收集目标系统或网络的信息&#xff0c;例如域名、IP地址、子网结构、网络拓扑等。 信息收集&#xff08;Informa…

开源博客项目Blog .NET Core源码学习(4:生成验证码)

开源博客项目Blog中的后台管理登录界面中支持输入验证码&#xff08;如下图所示&#xff09;&#xff0c;本文学习并记录项目中验证码的生成及调用方式。   博客项目中调用VerifyCode类生成验证码&#xff0c;该类位于App.Framwork项目中&#xff0c;命名空间为App.Framwork…