二分_算法

二分算法

概念

二分算法(Binary Search),也被称为折半查找,是一种高效的搜索算法,用于在已排序的数据集中查找特定元素。它的工作原理是将数据集分成两半,然后在其中一半中继续查找,以确定目标元素是否存在。

算法流程详述

二分条件

在一个有序的序列中;一个待查找的值x;

过程

取序列的两端,为l,r;

(l,r代表的可能是数组下标或者实在的数值,此处以数组下标为例,数组为a[])一般是闭区间。

取(l+r)/2为mid,a[mid]与x比较。进而更改l,r的值。

接着再求新的mid,以此类推,周而复始。

最终,l或r就为所求值x,或最接近x的值。

细节剖析

a[mid]与x的比较。

主要就两种情况,大于等于(小于),小于等于(大于);

(>=)这个求,不小于x的最小位置。与(<)是等价的,因为模板中的那个else。

(<=),求不大于x的最大的那个位置,与(>)等价。

模板

最小位置

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

过程描述

设置l,r端点。

取中间值mid,如果(<),说明mid在x的左边,因为没有(=),且是闭区间(闭区间,代表端点是可以取的)l=mid+1;

如果是else,说明在x右边,且mid可能就是为x,就不能像l那样+1,所以r=mid;

当在不断折半后,遇到只有两个值的时候,即l+1=r时。(这里,我们假设前提是,这个序列里存在x,那么此时,l,r里,一定有x)。分三种情况,

  1. l=x,r!=x,则x<r;

那么折半后,mid=l,满足else情况,那,r=mid=l;,之后循环停止。

  1. l!=x,r=x,l<x;

那么折半后,mid=l,满足if情况,那,l=mid+1=r;,之后循环停止。

  1. l=x,r=x,l=r=x;

那么折半后,mid=l,满足else情况,那,r=mid=l;,之后循环停止。

三者都能得到,正确的结果,没什么问题。

但在求最大位置的时候,就会出现问题。

这里,循环停止条件是l==r,所以l,r都是一样。

那么为什么这么肯定,l就是x得最小位置呢?

注意到,l每次变动的条件是(<),说明每次,l的左边的元素都不会比l所在的元素大。所以,l才是最小位置。

过程描述

我们先按照最小位置的模板进行,过程推演。只是把check的那个if语句写成,a[mid]<=x,和l和r取值做出相应的改变如下

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

照常,我们取mid,接着我直接过度到最后,只有两个元素的情况,即l+1=r。因为问题就是出现在这里。

还是三种情况。

  1. l=x,r!=x,则x<r;

那么折半后,mid=l,满足if情况,那,l=mid;依旧是l+1=r,之后就是死循环。

  1. l!=x,r=x,l<x;

那么折半后,mid=l,满足if情况,那,l=mid;,之后死循环循环。

  1. l=x,r=x,l=r=x;

那么折半后,mid=l,满足if情况,那,l=mid;,之后死循环。

总之,不管如何,总会出现死循环。导致这个模板出问题。

那么为什么会出现这个问题呢?

原因就在(l+r)/2这里,因为计算机是向下取整,导致,每次取得都是mid=l。

导致,每次都是满足if情况,而无法有效的更改l。

为了解决这个死循环问题。

就有的两种模板,还有一种模板我没有吃透,故不列出。

1号模板

最大位置1

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

可以看到,对mid的取值方式进行了改动。

因为死循环主要是不能向上取整导致的,所以我们给它加上个1,就可以在,l+1=r的时候,使得mid=r了;

那么再进行那三种情况的演算,就可以得到正确答案了。

这里不再累述,可以翻上去对照着算。

2号模板

最大位置2

int l1 = l, r1 = n; while (l1 + 1 < r1) { int mid = l1 + r1 >> 1; if (a[mid] <= x) l1 = mid; else r1 = mid; }

这里,改动了两处,循环条件和l,r的赋值。

死循环,是因为两个相邻的元素,取mid的时候,出现的问题。

那么,我们可以使得l,r在最后不相邻,就可以解决问题。

前面,我有提到,我们的l,r是闭区间。这里我们就可以采取左闭右开的区间。

即,r值是取不到。真实的有序区间是l到r-1;

那么当l+1=r的时候,说明就已经到最后一个值了,跟前面的1号模板,的l=r是等价的。

而这里因为,是开一侧的区间,就杜绝了相邻的情况。

这里进行演算一下。

取mid,如果满足if,说明在x的左边,因为有(=),所以可能mid==x,所以,就不+1,直接l=mid;

如果满足else,则说明在x的右边,比x大;且没有(=),本该r=mid-1的。

但r是开放的一侧,是取不到的,则r=mid。

我们再来到,最后时刻,就是只有两个元素的时候。

此时,l+2=r。

mid=(l+r)/2,为l,r中间的值rt;

也就是1号模板中的r了。即最右边的值。

再对照那三种情况

  1. l=x,rt!=x,则x<r;

那么折半后,mid=rt,满足else情况,那r=rt,因为循环条件为l+1<r,此时,循环就停止了,正确值为l;

  1. l!=x,rt=x,l<x;

那么折半后,mid=rt,满足if情况,那,l=mid;,循环停止,正确值为l。

  1. l=x,rt=x,l=rt=x;

那么折半后,mid=rt,满足if情况,那,l=rt;,之后循环停止,正确值为l。

那么为什么,肯定,l就是最大位置呢。这里,三种情况的第三个情况,最能说明问题。当有多个x,总是能取到最右边的那个元素。

进而确定最大位置。

二分特性

使用二分需要满足两个条件。一个是有界,一个是单调。

对于check函数,其作用就是对mid进行检验是否满足条件。

再根据这个结果去调制区间端点。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值