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

本文是学习算法的笔记,《数据结构与算法之美》,极客时间的课程

通过IP地址来查找IP归属地功能,不知道你用过没?没用过也没关系,打开百度,在搜索框里随便输入一个IP地址,就会看到它的归属地。

这个功能并不复杂,它是通过维护一个很大的IP地址库来实现。地址库中包括IP地址范围和归属地的对应关系。

当我们想要查询202.102.133.13这个IP地址的归属地时,我们就在地址库中搜索,发到IP地址落在[202.102.133.0, 202.102.133.255] 这个地址范围内,那我们就可以将这个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] 山东青岛
    现在我的问题是,在庞大地址库中逐一比对IP地址所在的区间,是非常耗时的。假设我们有12万条这样的IP区间与归属地的对应关系,如何快速出一个IP地址的归属地呢?

学完今天的内容,你就会发现这个问题其实很简单。

上一节我讲了二分查找的原理,并且介绍了最简单的一种二分查找的代码实现。今天我们来讲几种二分查找的变形问题。二分查找(下):如何快速定位IP对应的省份地址?_第1张图片
需要特别说明的一点,为了简化讲解,今天的内容,我都以数据从小到大排列为前提,如果要处理的数据是从大到小,解决的思路也是一样的。

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

100个人写二分查找,就会有100种实现。网上很多关于变形的二分查找实现方法,有很多写的非常简洁,比如正面这人写法。但是,尽管简洁,理解起来却非常烧脑,也很容易写错。

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(a[mid] < value){
			low = mid + 1;
		}else{
			if(mid == 0 || ( a[mid - 1] != value)){
				return mid;
			}else{
				high = mid - 1;
			}
		}
		return -1;
	}
}

这段代码,是不是比较容易理解些,**很多人觉得变形的二分查找很难写,主要原因是太追求第一种那样完美、简洁的写法。**而对于我们做工程开发的人来说,代码易懂、没 bug ,其实更重要,所以我觉得第二种写法更好。
(在学习这节的时候,本人用java实现的,如有谬误请指正)

	// 查找第一个等于给定值的元素
	private int binarySearchFirst(int[] a, int value) {
		int n = a.length;
		int low = 0;
		int high = n - 1;
		while (low <= high) {
			int mid = (low + high) / 2;
			if (a[mid] == value) {
				if (a[low] == value) {
					return low;
				}
				if(a[mid - 1] != value) {
					return mid;
				}else {
					high = mid -1;
				}
			} else if (a[mid] < value) {
				low = mid + 1;
			} else {
				high = mid - 1;
			}
		}
		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(a[mid] < value){
			low = mid + 1;
		}else{
			if(mid == 0 || ( a[mid + 1] != value)){
				return mid;
			}else{
				low = mid + 1;
			}
		}
		return -1;
	}
}

(在学习这节的时候,本人用java实现的,如有谬误请指正)

	// 查找最后一个等于给定值的元素
	private int binarySearchLast(int[] a, int value) {
		int n = a.length;
		int low = 0;
		int high = n - 1;
		while (low <= high) {
			int mid = (low + high) / 2;
			if (a[mid] == value) {
				if (a[high] == value) {
					return high;
				}
				
				if(a[mid + 1] != value) {
					return mid;
				}else {
					low = mid + 1;
				}
			} else if (a[mid] < value) {
				low = mid + 1;
			} else {
				high = 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;
}

(在学习这节的时候,本人用java实现的,如有谬误请指正)

	// 第一个大于等于
	private int greaterOrEqualFirst(int[]a, int value) {
		int n = a.length;
		int low = 0;
		int high = n -1;
		// 判断首尾元素与value的大小关系
		if(value <= a[low]) {
			return low;
		}
		if(value > a[high]) {
			return -1;
		}
		// 进入循环之后,保证 a[low] < value <= a[high]
		while(low + 1 != high) {
			int mid = (low + high) / 2;
			if(a[mid] >= value) {
				high = mid ;
			}else {
				low = mid ;
			}
		}
		return high;
	}

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

这个思路和上一个类似

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 == n -1 || ( a[mid + 1] > value)){
				return mid;
			}else{
				low = mid + 1;
			}
		}else{
			high = mid - 1;
		}		
	}
	return -1;
}

(在学习这节的时候,本人用java实现的,如有谬误请指正)

	// 最后一个小于等于
	private int lessOrEqualLast(int[]a, int value) {
		int n = a.length;
		int low = 0;
		int high = n -1;
		if(value < a[low]) {
			return -1;
		}
		if(value >= a[high]) {
			return high;
		}
		while(low + 1 != high) {
			int mid = (low + high) / 2;
			if(a[mid] <= value) {
				low = mid;
			}else {
				high = mid;
			}
		}
		return low;
	}

回答开篇

好了,现在我们回头看开篇的问题:如何快速定位出一个IP地址的归属地?

现在这个问题很简单了,如果IP区间与归属地的对应关系不经常更新,我们可以先预处理这12万条数据,让其按照IP从小到大排序。如何排序呢?我们知道,IP地址可以转化为32位的整型数。所以,我们可以将起始地址,按照对应的事型值的大小关系,从小到大进行排序。

然后这个问题就可以转化为我刚讲的第四种变形问题“在有序数组中,查找最后一个小于等于某个给定值的元素”了。

你可能感兴趣的:(算法与数据结构,二分查找,变体,算法)