二分查找的原理、实现与应用

基本思想

猜数字游戏

给定一个整数,在 0~100 的范围内,如何猜才能用最少次数猜中?

次数猜测范围中间数对比大小
第一次0-994949>20
第二次0-482424>20
第三次0-231717<20
第四次18-2320

我们可以通过计算得到,猜 0-100 的整数,最多猜测 log2100 次,也就是 7 次内必中。
log函数图形
观察log函数图形可知,数据量极大的情况下,猜测次数依旧不会很大,例如,当我们需要猜的数字在0~4,294,967,296之间,通过计算可知,最多猜测 32 次猜中。

实现无重复数据的二分查找

循环实现二分查找
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) {
			return mid;
		} else if (a[mid] < value) {
			low = mid + 1;
		} else {
			high = mid - 1;
		}
	}

	return -1;
}

要写出正确的二分查找代码,需要格外注意以下要点:

  1. 循环结束条件必定为low <= high
  2. 在取中间值时,最好不要直接相加除 2,数据很大的情况下直接相加可能导致数据溢出。
  3. 在得到下一个查询区间时,lowhigh的取值不能直接等于中间值,否则会导致死循环。
    例如:当low=3.high=3,array[3]!=value时
递归实现
// 二分查找的递归实现
public int bsearch(int[] a, int n, int val) {
	return bsearchInternally(a, 0, n - 1, val);
}

private int bsearchInternally(int[] a, int low, int high, int value) {
	if (low > high) return -1;

	int mid =  low + ((high - low) >> 1);
	if (a[mid] == value) {
		return mid;
	} else if (a[mid] < value) {
		return bsearchInternally(a, mid+1, high, value);
	} else {
		return bsearchInternally(a, low, mid-1, value);
	}
}

二分查找的时间复杂度

二分查找的时间复杂度与查找次数直接相关,而查找次数则与查找区间大小的变化过程相关,首先看一下数据量为n的情况下的查找区间变化:

第一次第二次第三次第四次……第k次
n n 2 \frac{n}{2} 2n n 4 \frac{n}{4} 4n n 8 \frac{n}{8} 8n…… n 2 k \frac{n}{2^k} 2kn

根据表格可得,k=log2n,即时间复杂度为O(log n)

二分查找的局限性

  1. 二分查找需要能够快速定位区间位置,即随机访问复杂度必须为 O(1),依赖顺序表,链表则不行。
  2. 只针对有序数据,并且最好是静态数据,没有频繁的插入的动态数据,可以一次排序,多次查找。
  3. 数据量极小时,不适合二分查找。
  4. 数据量过大也不适合二分查找,因为二分查找依赖顺序表,数据过大无法加载到内存中。

二分查找变形问题

数据均以正序排序

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

思路一:
①进行二分分区,凡是中间数大于等于给定值,则所求元素在左边,反之在右边。
②二分结束,得到 low 位置为第一个大于等于给定值的元素。
③判断 low 位置是否在数据范围内,该位置的值是否为给定值。

思路二:
①二分分区,三种情况:中间数大于、小于、等于给定值
②当等于给定值时,判断中间数是否为第一个数,再判断中间数前一个数是否等于给定值。

//代码一
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;
}
查找最后一个值等于给定值的元素
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;
}
查找第一个大于等于给定值的元素
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 if (a[mid] < value) {
			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;
}

应用

二分查找适合近似查找,例如开平方,查找第一个小于给定值等。

查找给定值等问题通常使用散列表实现。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值