0
点赞
收藏
分享

微信扫一扫

算法笔记(三)——二分查找(超详细,附带模板)

NicoalsNC 2022-04-21 阅读 55

                             未来属于那些相信梦想并愿意为之付诸行动的人

                                                 


 

前言

整数二分法

指定的数字(binary_search)

第一个大于等于X的位置(lower_bound/upper_bound)

不大于X的最后一个位置

数的范围

整数二分模板

浮点数二分法

 浮点数二分法模板

总结:


前言

整数二分法

 为了更好的理解这个查询过程,接下来我们来模拟这个二分查找过程.

指定的数字(binary_search)

现在我们需要从序列A={1,5,6,8,9,11,15,16,20}中查找数字11的位置,其中序列是0下标是0到8(下标1到9也是没问题的大家下去可以模拟一下)

1.[left,right]=[0,8],因此下标中点是mid=(left+right)/2=4;A[mid]=A[4]=9;9<11;说明需要在    [mid+1,right]范围内继续找,因此left=mid+1=5;

2.[left,right]=[5,8],因此下标中点是mid=(left+right)/2=6;A[mid]=A[6]=15;15>11;说明需要在[left,mid-1]范围内继续查找,因此right=mid-1=5;

3.[left,right]=[5,5],因此下标中点mid=(left+right)/2=5;A[mid]=A[5]=11;11=11;说明找到了需要查找的数X,返回下标5

ok,有了上面的基础下面我们来写代码吧。

int binarySearch(int A[],int left,int right,int x)
{
   int mid;//mid为left和right的中点
  while(left<=right)
  {
    mid=(left+right)/2;   //取中点,这里一般写成left+(right-left)/2;避免溢出
    if(A[mid]==x)  return mid;//找到x,返回下标
    else if(A[mid]>x)  right=mid-1;//往左子区间[left,mid-1]查找
    else       left=mid+1;  //往右子区间[mid+1,right]查找
  }
  return -1;//查找失败返回-1
}

如果序列是递减的,只需要把A[mid]>x改为A[mid]<x

二分法也可以用递归形式进行

//二分区间为左闭右闭的[left,right],传入的初值为[0,n-1]
int binarySearch(int* a, int left, int right, int key)
{
	while (left < right)
	{
		int mid = left + ((right - left) >> 1);
		int number = a[mid];
		if (number < key)
		{
			return binarySearch(a, mid + 1, right, key);
		}
		else if (number > key)
		{
			return binarySearch(a, left, mid - 1, key);
		}
		else
			return mid;
	}
}

这里了解了,我们接下来要更进一步的讨论:如果递增序列A中的元素可能重复,那么如何对给定的欲查询元素x,求出序列中第一个大于等于x的元素的位置L以及不大于X的最后一个位置?

第一个大于等于X的位置(lower_bound/upper_bound)

做法其实和前面的很类似,下面我们来分析一下,假设当前的二分区间为左闭右闭区间[left,right],那么可以根据mid位置处的元素与欲查询元素x的大小来判断应当往哪个区间查找:

代码:

//A[]为递增,x为查询数字;
//二分上下界为左闭右闭的[left,right],传入的初值为[0,n]
int low_bound(int A[],int left,int right,int x)
{
 int mid;
  while(left<right)
 {
   mid=(left+right)/2;
   if(A[mid]>=x)  right=mid;//中间的数大于等于x,往左边区间找[left,mid]
   else           left=mid+1;//中间的数小于x,往右边区间找[mid+1,right]
 }
  return left;
}

拓展: 其实这就是C++中lower_bound()函数的用法,返回第一个大于等于x数的位置,不存在则返回最后一个数的下一个位置;对应的也有个upper_bound()函数的用法,它是返回第一个大于x数的位置;这个大家可以去实现下,其实就是A[mid]>=x换成A[mid]>x就可以了.这两个函数大家可以去了解下,具体实现上面其实已经介绍了.

不大于X的最后一个位置

这个实现就比上面的稍微难一点了.所以我这里需要引入个例题:

数的范围

简述下题意就是:就是查询一个数的起始位置和终止位置,不存在这个数就返回-1,所以这里取值范围不需要向上面一样取到n(这里不需要输出最后一个数的下一个位置);

需要写两个二分,一个需要找到>=x的第一个数,另一个需要找到<=x的最后一个数。查找不小于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;
}

查找不大于x的最后一个位置,便不容易了:

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

这里我看到了一位小哥写的解释,超级清楚,拿过来给大家看一下:AcWing 789. 数的范围(详细分析二分过程) - AcWing(大家也可以去这看)

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;
 }

到这里核心的代码已经展示完毕,大家可以下去自己去做做.

整数二分模板

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;
}

浮点数二分法

首先介绍如何计算根号2的近似值。

对于f(x)=x^2来说,在x属于[1,2]范围内,f(x)是随着x的增大而增大的,这就给使用二分法创造了条件,即可以用如下策略来逼近根号二的值。(这里不妨以精确到10^-5为例)

令浮点数left和right的初值分别是1和2,然后根据left和right的中点mid处f(x)的值与2的大小来选择子区间进行逼近.

 上面两个步骤当right-left<10^-5时结束.

代码:

const double eps=1e-5;
double f(double x)
{
   return x*x;
}
double calsqrt()
{  
  double left=1,right=2,mid;
  while(right-left>eps)
  { 
   mid=(left+right)/2;
   if(f(mid)>2)  right=mid;
   else         left=mid;
  }
  return mid;
 }

 浮点数二分法模板

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

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

总结:

 

举报

相关推荐

0 条评论