二分查找(下):如何快速定位 IP 对应的省份地址?
通过 IP 地址查找 IP 归属地的功能,是通过维护一个很大的 IP 地址库来实现的。地址库中包括 IP 地址范围和归属地的对应关系。
```
[202.102.133.0, 202.102.133.255] 山东东营市
[202.102.135.0, 202.102.136.255] 山东烟台
[202.102.156.34, 202.102.157.255] 山东青岛
[202.102.48.0, 202.102.48.255] 江苏宿迁
[202.102.49.15, 202.102.51.251] 江苏泰州
[202.102.56.0, 202.102.56.255] 江苏连云港
```
但是在庞大的地址库中逐一比对 IP 地址所在的区间,是非常耗时的。**假设我们有 12 完条这样的 IP 区间与归属地的对应关系,如何快速定位出一个 IP 地址的归属地呢?**
二分查找的变形问题
上一节的二分查找的代码只是最简单的一种情况,在不存在重复元素的数组中,查找 值等于给定值 的元素。最简单的二分查找写起来确实不难,但是,二分查找的变形问题就没那么好写了。
这里有四种常见的二分查找的变形问题。
* 查找第一个 `值等于给定值` 的元素
* 查找最后一个 `值等于给定值` 的元素
* 查找第一个 `大于等于给定值` 的元素
* 查找最后一个 `小于等于给定值` 的元素
* 此节的内容,都是以数据从小到大排列为前提!(从小到大和从大到小思路是一样的)
一、查找第一个 `值等于给定值` 的元素
自己做了一下,要考虑的东西有点多,越写越乱。
假定有下面一个有序数组,其中,a[5],a[6],a[7] 的值都等于 8,是重复的数据,我们希望查找第一个等于 8 的数据,也就是下标是 5 的元素。
如果按上一篇中的最简单的二分查找来查询,返回值是 a[7]。
但是 a[7] 并不是第一个等于 8 的元素,a[5] 才是。
所以需要对上一篇中的二分查找代码做一些改动。
```java
//这是一个非常简洁的写法,但是可读性比较差,难于理解
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 {
//为什么下面的找到值的时候会 return low?
//因为这里的low = mid +1 实际上就又让low的值等于上一轮的 mid
low = mid + 1;
}
}
if (low < n && a[low]==value) return low;
else return -1;
}
```
**换一种易于理解的实现方式**
```java
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;
}
```
二、查找最后一个值等于给定值的元素
```java
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;
}
```
与第一种写法思路一致。
网友评论