算法初步--二分法(以PAT考试A1010为例题)

算法初步--二分法(以PAT考试A1010为例题)。二分法的经典问题、经典问题的微扩展、经典问题的总结、PAT考试真题A1010、后记。
摘要由CSDN通过智能技术生成

二分法的经典问题

从一个经典问题开始:给定一个严格递增序列,要求找到当中的某个元素所在的位置。

具体点说:给定序列A[6] = {1, 2, 3, 5, 7, 9},找到 x=2所在的位置。
二分法即是为解决这类有序序列所准备的。基本原理是这样的:

在已知序列中,查找区间为[left, right] = [0, 5],注意下标从0开始。

检查中间元素A[mid]是否为所找元素,式中mid = (left + right)/2 = (0+5)/2 = 2。显然A[mid] = 3>x,则将查找区间更新为[0, 1],即right = mid-1。

再次检查中间元素A[(0+1)/2] = A[0] = 1是否为所找元素x,显然A[0]<x,则跟新区间左边界left = mid+1=1,新区间为[1,1]。

再次检查中间元素A[mid] = A[(1+1)/2] = A[1] = 2,即为所找元素x。

由此引申为C的代码如下:

//定义如下函数,用来返回所找元素x的下标
//A[]为严格递增序列,[left,right]为所查区间,x为所找元素 
int binarySort(int A[], int left, int right, int x){
   
	int mid; 
	while(left <= right){
   
		mid = (left + right) / 2; //区间中间元素下标 
		if(A[mid] == x) return mid; // 找到元素,返回下标 
		else if(A[mid] > x) right = mid - 1;//所找元素在当前区间左侧,修改右边界 
		else left = mid + 1;
	}
	return -1; // 查找失败,返回-1 
} 

经典问题的微扩展

以上解决了严格递增序列,再思考一个问题:给定一个递增序列(非严格递增)找到元素x的所在下标,具体问题如下:

给定A[5] = {1, 2, 2, 4, 6},如要求找x = 2的下标,应给出答案为区间[1,3),之所以区间设计为左闭右开是为了解决这样一个问题:

要求找x = 3的下标,那么答案应该为[3,3)。显然,当所找元素不存在时,也应该返回一个它应该在的位置,所以区间设计为左闭右开(从这点出发,设计成左开右闭也是可以的)。

下面我们来分析上述问题:
显然,任务从经典问题的寻找一个元素下标,变为找到一个区间的左右边界,所以我们分别解决两个任务:左闭边界和右开边界:

  • 任务A:寻找左闭区间下标
    再次解读我们需要做的事情,将问题转化为:从左往右找到第一个大于等于x的元素下标。

    这样的一个问题和我们第一个经典问题类似了,区别在于条件不同。这里,我们找到一个大于等于x的还不够,当找到这样一个元素时,说明我们仍然需要将查找区间[left,right]向左移,即改变右边界,直到确定到这是第一个满足条件的元素。我们一边看C代码一边感受这里的变化:

//递增序列A,找到第一个大于等于x的元素下标 
int leftBound(int A[], int left, int right, int x){
   
	int mid;
	while(left < right){
    // 从 <= 变为 <
		mid = (left+right) / 2;
		if(A[mid] >= x) right = mid; // 右边界修改不再是 mid-1
		else left = mid + 1;
	}
	return left; // 返回夹出来的边界,即下标
} 
  1. while(left < right)从<=变为<,仔细思考原因:上一个经典问题中,我们需要在left=right时,进一次循环,从而能return mid,在这里我们并不需要return mid,当left=right时,并不需要再次进入循环,因为我们在while循环结束后有 return left 语句,并且left=right时的left即为所求。
  2. if(A[mid] >= x) right = mid;这里当中间元素A[mid]≥x,说明所找x在区间左侧,则改变右边界,但是右边界并不能改为 mid-1,因为你不知道这个A[mid]是不是就是你找的那个第一个满足条件的元素,所以下次查找时,区间应该包括这个元素。
  3. 最后是return left;这里返回边界,事实上返回左边界还是返回右边界都一样,因为这条语句是在while循环后执行的,而while循环结束的条件是left==right。
  • 任务B:寻找右开区间下标
    同样地解读任务,转化为:找到第一个大于x的元素的下标,这个问题和上一个问题极其类似,直接给出C代码,我们能更直观的感受到究竟有多类似:
//递增序列A,找到第一个大于x的元素下标 
int rightBound
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值