二分思想的本质和基本应用

整数二分

二分思想

二分思想主要应用于找边界问题。
为了形象阐述二分思想以及它的应用,我打算举一个例子来说明。

假设有一个数列q,在该数列中存在一个区间,该区间左端元素的序号为l,右端元素的序号为r。

存在一个临界点p(边界),使得其右边的所有元素(包含p)都满足性质A,而其他元素都不满足性质A。

在这里插入图片描述
为了确定边界p的位置,我们就可以采用二分思想,具体做法如下:

  1. 取该区间的中间点mid:
    mid=l+r2mid=\frac{l+r}2mid=2l+r
  2. 检测序号为mid的元素是否满足性质A:
    check(q[mid])={true          mid元素满足性质Afalse      mid元素不满足性质Acheck(q\lbrack mid\rbrack)=\left\{\begin{array}{l}true\;\;\;\;\;mid\mathrm{元素满足性质}A\\false\;\;\;mid\mathrm{元素不满足性质}A\end{array}\right.check(q[mid])={truemidAfalsemidA
  • 如果mid元素满足性质A,说明它在p点的右边(包含p),此时,可以缩小查找范围,缩小查找区间,变更后的查找区间应为:
    [l,  r=mid]\lbrack l,\;r=mid\rbrack[l,r=mid]
  • 如果mid元素不满足性质A,说明它在p点的左边(不包含p),此时,也可以缩小查找范围,缩小查找区间,变更后的查找区间应为:
    [l=mid+1,  r]\lbrack l=mid+1,\;r\rbrack[l=mid+1,r]
  1. 检查l是否等于r,如果相等,结果就为临界点p;如果不等,重复步骤一、二、三。

伪代码实现

int binary_search1(int q[], int l, int r) {
	while (l < r) {
		int mid = l + r >> 1;
		if (check(q[mid]))	r = mid;
		else  l = mid + 1;
	}
	return l;
}

左边界和右边界问题(重点重点!!!!)

前面我们讨论的是,假定临界点右边的元素(包含临界点)满足性质A,而其他元素不满足的情况。那如果我们考虑另一种情况,即:

考虑临界点左边的元素(包含临界点)满足性质A,而其他元素不满足。

在这里插入图片描述
此时,就出现了两种情况,也就是左边界问题和右边界问题。
我把第一种情况称为右边界问题,第二种情况称为左边界问题。
那么, 左边界问题和右边界问题的解决方法是否一致呢?

答:基本思想一致,但实现细节有差别(这一点差别很难发现,很容易造成程序死循环,形成BUG!!!!)

对于左边界问题,为了确定p的位置,具体做法如下:

  1. 取该区间的中间点mid:
    mid=l+r+12mid=\frac{l+r+1}2mid=2l+r+1(为什么是(l+r+1)/2 ? 后面再解释)
  2. 检测序号为mid的元素是否满足性质A:
    check(q[mid])={true          mid元素满足性质Afalse      mid元素不满足性质Acheck(q\lbrack mid\rbrack)=\left\{\begin{array}{l}true\;\;\;\;\;mid\mathrm{元素满足性质}A\\false\;\;\;mid\mathrm{元素不满足性质}A\end{array}\right.check(q[mid])={truemidAfalsemidA
  • 如果mid元素满足性质A,说明它在p点的左边(包含p),此时,可以缩小查找范围,缩小查找区间,变更后的查找区间应为:
    [l=mid,r]\lbrack l=mid,r\rbrack[l=mid,r]
  • 如果mid元素不满足性质A,说明它在p点的右边(不包含p),此时,也可以缩小查找范围,缩小查找区间,变更后的查找区间应为:
    [l,r=mid−1]\lbrack l,r=mid-1\rbrack[l,r=mid1]
  1. 检查l是否等于r,如果相等,结果就为临界点p;如果不等,重复步骤一、二、三。

伪代码实现

int binary_search2(int q[], int l, int r) {
	while (l < r) {
		int mid = l + r + 1 >> 1;
		if (check(q[mid]))	l = mid;
		else  r = mid - 1;
	}
	return l;
}
左边界中mid取值问题

在左边界问题中,为何mid=l+r+12mid=\frac{l+r+1}2mid=2l+r+1?

/*对于左边界问题,如果我们让mid = (l + r) / 2,就有可能出现下面的问题:
	考虑下面程序在运行了几次循环之后,出现l = r - 1情况,此时mid = r - 1,
check(q[mid])也一定为true,那么l = mid = r - 1,于是就回到了最开始的情况,然后程序
就会陷入死循环。*/
int binary_search2(int q[], int l, int r) {
	while (l < r) {
		int mid = l + r >> 1;
		if (check(q[mid]))	l = mid;
		else  r = mid - 1;
	}
	return l;
}
//解决方法就是让mid = (l + r + 1) / 2,即可解决该问题。
int binary_search2(int q[], int l, int r) {
	while (l < r) {
		int mid = l + r + 1>> 1;
		if (check(q[mid]))	l = mid;
		else  r = mid - 1;
	}
	return l;
}

整数二分问题

给定一个按照升序排列的长度为 n 的整数数组p,以及 q 个查询。返回每一个待查找元素 k 的起始位置和终止位置(位置从 0 开始计数)。如果数组中不存在该元素,则返回 -1 -1。

解题思路:

采用二分思想,在该数组上定义一个性质:大于等于k,可以得到右边界点p0;然后在数组上再定义一个性质:小于等于k,可以得到左边界点p1。最后,p0就是待查找元素的终止位置,p1就是待查找元素的起始位置。

代码实现

#include<iostream>
using namespace std;

const int N=10000;
int p[N]; 
int n, q; //n为数组长度,q为查询次数。

int main()
{
    scanf("%d%d", &n, &q);
    for(int i=0; i < n; i++)    scanf("%d", &p[i]); //为p[n]赋值,p[n]为升序整数数组。
    
    while(q--){
        int x;
        scanf("%d", &x);	//输入待查找的元素
        int l = 0, r = n - 1;	//初始化查找区间的左右端点序号
        //对于右边界点问题,采用binary_search1()操作
        while(l < r){                 
            int mid = l + r >> 1;
            if(p[mid] >= x) r = mid;
            else    l = mid + 1;
        }
        if(p[l] != x)   cout << "-1 -1" << endl;
        else{
            cout << l << " ";
            int l = 0, r = n - 1;
            //对于左边界点问题,采用binary_search2()操作
            while(l < r){
                int mid = (l + r + 1) >> 1;
                if(p[mid] <= x) l = mid;
                else    r = mid - 1;
            } 
            cout << l << endl;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值