首页 > 其他分享 >二分查找

二分查找

时间:2022-08-22 14:25:28浏览次数:49  
标签:二分 int mid 更新 边界点 查找 区间 性质

二分查找

二分查找分为整数二分和小数二分,其中整数二分涉及的边界问题比较多,理解起来相对复杂。

# 整数二分

如果可以找到一个性质,可以把区间一分为二,一半满足性质一半不满足。二分可以找到这个性质的边界,可以是①也可以是②。

这里这个分界点①和②就分两种情况讨论。

# 寻找边界点①——右边界

  1. mid = (l + r + 1) >> 1

  2. if( check(mid) ) 有两种结果

    • true >>> [mid, r] >>> l = mid

      mid满足性质,因此mid落在红色区间,并且在红色区间可以取到边界点①,因此下一个区间包含mid,区间为[mid, r]

    • false >>> [l, mid - 1] >>> r = mid - 1

      mid不满足性质,因此mid落在绿色区间,在绿色区间内不可能取到边界点①,因此下一个区间不包含mid,区间为[l, mid - 1]

# 寻找边界点②——左边界

  1. mid = (l + r) >> 1

  2. if ( check(mid) ) 有两种结果

    • true >>> [l, mid] >>> r = mid

      mid满足性质,因此mid落在绿色区间,并且在绿色区间内可以取到边界点②,所以下一个区间包含mid,区间为[l, mid]

    • false >>> [mid + 1, r] >>> l = mid + 1

      mid不满足性质,因此mid落在红色区间,并且在红色区间内不可能取到边界点②,所以下一个区间不包含mid,区间为[mid + 1, r]

# 步骤

一般情况下,先写一个mid的取值,然后再写check()函数的实现,及需要满足的性质。根据check()的返回值判断区间如何划分。

A. 先确定序列的上下界

int l = 0, r = n - 1;

B. 开始查找,直接写出一个mid,之后再看看用不用+1

int l = 0, r = n - 1;
while( l < r ) {
    int mid = l + r >> 1;
}

C. 判断是要找性质的左边界还是右边界。

比如找左边界,即第一个>=x的数。这里就开始讨论两种区间更新的情况,可以直接画出序列上面满足性质和不满足性质的区间图,然后再判断l, r的更新方式。

  1. 如果mid落在>=x的区间上,则xmid的左边,同时midx在同个区间上,因此收缩rr = mid
  2. 如果mid没有落在>=x的区间上,则xmid的右边,而且midx不在同一个区间上,因此收缩ll = mid + 1

最后再看看mid的更新方式要不要+1,可以直接记:寻找性质左边界时不需要 +1,或者是当更新区间的时候如果没有出现l, r其中一个更新为“mid - 1”,则不用在前面+ 1

int l = 0, r = n - 1;
while( l < r ) {
    int mid = l + r >> 1;
    if(q[mid] >= x)  r = mid;
    else l = mid + 1;
}

D. 在查找结束后,lr会重叠指向我们要查找的性质的边界。若目标x存在,则可以直接输出x的位置。若目标不存在,则会返回第一个大于x的数。

int l = 0, r = n - 1;
while( l < r ) {
    int mid = l + r >> 1;
    if(q[mid] >= x)  r = mid;
    else l = mid + 1;
}	//then l == r
if(q[l] == x) cout << l << endl;

# 需要斟酌的细节

mid = (l + r) >> 1 还是 mid = (l + r + 1) >> 1

还是这个图

在寻找边界点①的时候,当更新到l + 1= r的时候,即lr相邻时,如果mid满足红色性质,则更新区间为[mid, r],也就是把l更新成mid

如果使用mid = (l + r) >> 1,由于整数除法是下取整, 因此把l更新成mid会有:l = mid = (l + (l+1) ) >> 1 = (2l + 1) >> 1 = l.

也就是l = l,相当于没有更新区间,因此会不停的执行mid = (l + r) >> 1l = mid陷入死循环。

因此需要使用mid = (l + r + 1) >> 1使得mid的结果是区间的一半的上取整。这样区间的左边界才会收缩。

可以这么理解记忆:

  • 当寻找性质的左边界的时候,由于更新区间为r = mid, l = mid + 1,此时mid = l + r >> 1,不需要加一。

    • 为什么?
    • 因为不加一的时候mid是下取整
    • 更新r时直接赋值为mid的也会下取整,因此r一定会收缩,不会死循环。
    • 更新l时是赋值为mid + 1会上取整,因此l也一定会收缩,所以mid不需要加一。
  • 当寻找性质的右边界的时候,由于更新区间为l = mid, r = mid - 1,此时mid = l + r + 1 >> 2,需要加一。

    • 为什么?
    • 因为加一的时候mid是上取整
    • 更新l时直接赋值为mid也会上取整,因此l一定会收缩,不会死循环。
    • 更新r时赋值为mid - 1会抵消掉上取整,因此r也一定会收缩。

# 查找结束时 l 和 r 的位置

由于循环的条件时l < r,然后更新区间都会在[l, r]之间更跟新,因此最终循环结束时必定有l == r,因此最终的结果在 l 或者 r 上任意一个。

# 代码模板

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

2022.04.11

标签:二分,int,mid,更新,边界点,查找,区间,性质
From: https://www.cnblogs.com/Ethan-Code/p/16612631.html

相关文章

  • [四、Xcode界面]17代码的查找和替换
    热烈欢迎,请直接点击!!!进入博主AppStore主页,下载使用各个作品!!!注:博主将坚持每月上线一个新app!!!......
  • 学习笔记——wqs 二分
    前言前情提要概论这类问题的特点是,本来不需要求代价,我却二分出一个代价从而间接的满足题目中的某些限制。最显著的标志,就是⌈恰好选\(k\)个⌋的限制。这样说比较抽......
  • 【2022牛客多校】第五场 Crazy Thursday 二分+字符串哈希
    https://ac.nowcoder.com/acm/contest/33190/G题意给你一个长为n的字符串s,求s中分别以'k'、'f'、'c'结尾的回文串数量\(n<=5e5\)思路首先暴力枚举区间端点加判断,为......
  • 二叉树 查找第k大的数
    改造方法需在节点N中记录以节点N为根的子树的节点数numOfNodes,根节点记录整颗树的节点数目,则若根节点的左子树的numOfNodes刚好为k-1,那这个根节点的值即为目标值。注意......
  • 二分法查找
    importrandomclassSearch(object):defbinary_search(self,data,value):"""二分查找迭代法实现:paramdata:list待查找序列......
  • [编程题]项目经理(二分图)
    项目经理参考,模板'''二分图,匈牙利算法最大匹配数:最大匹配的匹配边的数目最小点覆盖数:选取最少的点,使任意一条边至少有一个端点被选择最大独立数:选取最多......
  • 436. 寻找右区间--LeetCode_二分
    来源:力扣(LeetCode)链接:https://leetcode.cn/problems/find-right-interval著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。题目大意是这样的 数组......
  • win32com:word操作之 通过文字查找段落
      练习:#遍历可以查找出所有包含关键字的段落#去掉遍历只查找到第一个包含关键字的段落fullrange=doc.Range()foriinrange(4):fullrange.Find.Execut......
  • 第6章 数组、排序和查找
    ​6.1 为什么需要数组Array     数组可以存放多个同一类型的数据,数组的数据类型是引用类型。6.2 数组的使用     ​​​​​​​1)使用方式1:动......
  • 275. H 指数 II--Leetcode_二分
    来源:力扣(LeetCode)链接:https://leetcode.cn/problems/h-index-ii著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。题目的大意是这样的 有一个升序......