看故事学算法-查找算法

二分查找法,最省内存的查找算法

如果通过地址来查找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地址所在的区间,是非常耗时的。假设我们有12w条数据,如何快速定位出一个ip地址的归属地呢?

在解决这个问题之前我么首先介绍下一般的二分查找法,以及优缺点

int bsearch(int arr[], int n, int value) {
	int low = 0;
	int high = n - 1;
	while (low <= high) {
		int mid = low + (high - low) / 2;
		if (value == arr[mid]) {
			return mid;
		}else if (arr[mid] > value) {
			high = mid-1;
		}else {
			low = mid + 1;
		}
	}
	return -1;
}

注意点

1.(low+high)/2 这种写法是有问题的如果是low和high特别大有可能精度丢失,可以使用low+(high-low)/2或者low+((high-low)>>1)
2.mid的取值问题,low=mid+1,high=mid-1。注意这里的 +1 和 -1,如果直接写成 low=mid 或者 high=mid,就可能会发生死循环。比如,当 high=3,low=3 时,如果 a[3] 不等于 value,就会导致一直循环不退出

普通二分法的局限

1.必须查找数组里面的数据,不能是链表,因为二分法需要按照下标随机访问数据,数组通过下标访问数据的时间复杂度为O(1),链表是O(n),所以如果使用链表的话,二分查找复杂度就变得很高
2.必须是有序数组所以,二分查找只能用在插入、删除操作不频繁,一次排序多次查找的场景中。针对动态变化的数据集合,二分查找将不再适用
3.数据量太小的情况不适合二分查找,有个例外就是如果数据之间的比较特别耗时的情况下不管数据量的大小都而可以用二分查找法。

example

如何从100万整数中查找指定的数,内存限制100M,每个数据占8字节,这种情况下内存特别有限,我们可以在1000万整数在内存中先排序后使用二分查找,当然这中情况适合于多次查找的情况,只查找一次的话一次遍历就找到了。

二分查找法的变形问题

常见的二分法变形问题

1.查找地一个值等于给定值得元素
2.查找最后一个值等于给定值得元素
3.查找第一个大于等于给定值得元素
4.查找最后一个小于等于给定值得元素

查找第一个值等于给定值得元素

int bsearch(int arr[], int n, int value) {
	int low = 0;
	int high = n - 1;
	while (low <= high) {
		int mid = low + (high - low) / 2;
		if (arr[mid] > value) {
			high = mid-1;
		}else if(arr[mid]<value){
			low = mid+1;
		}else {
			if (mid == 0 || arr[mid - 1] != value) return mid;
			else high = mid - 1;
		}
	}
	return -1;
}

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

int bsearch(int arr[], int n, int value) {
	int low = 0;
	int high = n - 1;
	while (low <= high) {
		int mid = low + (high - low) / 2;
		if (arr[mid] > value) {
			high = mid-1;
		}else if(arr[mid]<value){
			low = mid+1;
		}else {
			if ((mid == n - 1) || arr[mid + 1] != value) return mid;
			else low = mid + 1;
		}
	}
	return -1;
}

查找第一个大于等于给定值得数字

int bsearch(int arr[], int n, int value) {
	int low = 0;
	int high = n - 1;
	while (low <= high) {
		int mid = low + (high - low) / 2;
		if (arr[mid] >= value) {
			if (mid == 0 || arr[mid - 1] < value) return mid;
			else high = mid - 1;
		}else {
			low = mid + 1;
		}
	}
	return -1;
}

查找最后一个小于等于给定值得数字

int bsearch(int arr[], int n, int value) {
	int low = 0;
	int high = n - 1;
	while (low <= high) {
		int mid = low + (high - low) / 2;
		if (arr[mid] > value) {
			high = mid - 1;
		}else {
			if ((mid == n-1) || arr[mid + 1] > value) return mid;
			else low = mid + 1;
		}
	}
	return -1;
}

如何定位出一个ip地址的归属地呢?

现在这个问题清楚了,我们想把10w条数据预处理,让其按照其实ip从小到大排序,先把ip转化为32位整形,然后按照大小关系排序,然后这个问题就转化为在有序序数中查找最后一个小于等于某个给定值得元素了。当我们要查询ip归属地时,就可以二分查找,找到最后一个起始ip小于等于这个ip的ip区间,然后检查这个ip是否在这个ip区间,如果在就祛除对应的归属地,不在dehumidifier啊,就返回查找不到。虽然如此,但是大部分时候,凡是能用二分法解决的问题,我们更加倾向使用二叉查找树,虽然二分法内存更省,但是内存很便宜。对于二分法更多的更适合用近似查找问题,比如今天将的几种变种问题,用其他数据结构如散列表,二叉树,就比较难实现了。

课堂作业:
如何编程实现“求一个数的平方根”?要求精确到小数点后 6 位。
2.有序数组是一个循环数组,4,5,6,1,2,3这种情况,如何实现一个求值等于给定值得算法。

posted @ 2021-09-09 22:52  弄啥来  阅读(55)  评论(0)    收藏  举报