二分查找基础总结

一、基本思想

二分查找的基本思想是将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果x<a[n/2],则只要在数组a的左半部分继续搜索x,如果x>a[n/2],则只要在数组a的右半部搜索x.

二分查找的时间复杂度为:O(log2n)

我们可以根据一个例子来感受二分查找的优点:
A心里想着1-1000之间的一个数字,B来猜,可以问问题,A只能回答是或否,怎样猜问的问题次数最少?

  • 是1吗?是2吗?…是999吗?
    (如果是这种问法,猜出这个数字平均要问500次)
  • 大于500吗?大于250吗?…
    (这种问法将范围缩小到一半,最多只需要问10次!!!!即可得到该数字)

二、问题

1.在有序数组(非降序)中查找定值
  • 思考: 可以说这是最基础的一种问题,将有序数组二分,用中间值a[mid]与目标值target作比较,如果 a[mid] 小于target,则在右区间继续进行二分;如果a[mid]大于 target,则在左区间继续进行二分,如果a[mid]等于target,则返回其下标索引。

  • 代码

int search(int a[], int n, int target) {
	int left = 0, right = n - 1;     //定义左右边界(闭区间)
	while(left <= right) {
		int mid = left + (right - left) / 2;
		if(a[mid] == targrt) {
			return mid;
		} else if(a[mid] > target) {
			right = mid - 1;
		} else if(a[mid] < target) {
			left = mid + 1;
		}
	}
	return -1;
	//代码为了明显的展现出细节而使用else if,下面的代码也一样
}
  • 注意 细节真的很重要!!!!!!
    <1>.防止死循环: 很多刚接触二分查找的人也包括我自己,在mid是否加减1上迷迷糊糊,在循环条件left<=right下,mid+1,mid-1可以让我们不去考虑死循环和左右中点的情况。如果left = mid,就可能存在left与right相邻的情况,而mid = (left+right)/2,mid 取得值为 left ,left 又等于 mid… …(无限循环下去)
    <2>.注意溢出: 建议mid取值时使用 mid= left+(right - left)/2,这样可以防止因left、right过大而导致mid溢出。
2. 在有序数组(非降序)查找第一个等于定值的元素
  • 思考:举个例子a[4]={2,3,3,4,5},target=3,返回第一个等于定值的元素的下标索引 1 。如果是按上一问题那样求的话一定返回的是2,只进行了一次二分就结束查找。所以,对于这种问题,当a[mid]等于目标值时,不要着急结束查找,应继续查找该元素的左边是否还存在相同元素。
  • 代码
int search(int a[], int n, int target) {
	int left = 0, right = n - 1;
	while(left < right) {
		int mid = left + (right - left) / 2;
		if(a[mid] >= target) {
			right = mid;
		} else if(a[mid] < target) {
			left = mid + 1;
		}
	}
	return (a[left] == target) ? left : -1;
}
  • 注意:在循环条件left<right下,如果查找到a[mid]>=target,将右边界设于此处,继续进行查找,直到left==right结束循环, 可能数组中不存在目标值,所以最后应作出判断如果a[left]==target返回该元素的索引 left ,否则返回-1。
3.在有序数组(非降序)中查找最后一个等于定值的元素
  • 思考:与上一道题类似,当a[mid]==target时,不要着急结束查找,应继续查找该元素右边是否还存在相同元素。
  • 代码
int search(int a[], int n, int target) {
	int left = 0, right = n - 1;
	while(ledt < right) {
		int mid = left + (right - left + 1) / 2;
		if(a[mid] <= target) {
			left = mid;
		} else if(a[mid] > target) {
			right = mid - 1;
		}
	}
	return (a[left] == target) ? left : -1;
}
  • 注意:这里mid取值的时候用到(right - left + 1),这是为了防止取左中间点而出现死循环,因为当 a[mid] <= target 时,left=mid,一定会出现 left 与 right 相邻的情况,如果不加1,那么mid一定等于 left 的值,第一题中已经提到过,这样会无限循环下去。因为结束循环是当 left == right ,所以最后返回做判断时 a[left] 和 a[right] 都可以,数组中存在目标值返回其索引,否则返回-1。
4. 在有序数组(非降序)中查找第一个大于target的值
  • 思考:其实这几道题都是大同小异,重要的是对细节的理解。如果查找到a[mid]>target,不要着急结束查找,应将该mid当作右边界,继续在其左边进行查找。
  • 代码
int search(int a[], int n, int target) {
	int left = 0, right = n - 1;
	while(left < right) {
		int mid = left + (right - left) / 2;
		if(a[mid] > target) {
			right = mid;
		} else if(a[mid] <= target) {
			left = mid + 1;
		}
	}
	return (a[left] > target) ? left : -1;
}
  • 注意:这里对 a[mid] 大于 target 的处理,right = mid ,这使得 a[right] 始终大于target,直到left==right循环结束,这样 a[left] 一定为最小的大于target的值 (如果数组存在大于target的值)。
5. 在有序数组(非降序)中查找最后一个小于target的值
  • 思考:类似与前面提到的,在找到 a[mid] < target 时,不要着急结束查找,应将该mid 作为左边界继续进行二分。
  • 代码
int search(int a[], int n, int target) {
	int left = 0, right = n - 1;
	while(left < right) {
		int mid = left + (right - left + 1/ 2;
		if(a[mid] >= target) {
			right = mid - 1;
		}  else if(a[mid] < target) {
			left = mid;
		}
	}
	return (a[left] < target) ? left : -1;
}
  • 注意:这就…没什么再补充的了,需要注意的前面也都提到过,比如mid 取值里的 +1 ,防止出现死循环。

三、总结

对于这段时间对二分查找的学习以及练习做题的感受,真的感觉细节是魔鬼!!!!!!! 一不小心就不知道哪出错了 (T﹏T),在网上看了很多种写法,对细节的处理都不太一样,所以,我还是觉得理解最重要,不能生搬硬套。
还有一点要注意!!!
先上图
注意
所以做题时一定要对待查找的序列进行 排序

猜你喜欢

转载自blog.csdn.net/qq_45836906/article/details/105867955