二分法-手写二分-STL二分

本文深入讲解二分查找算法的不同变种及其应用场景,包括朴素二分查找、第一类和第二类二分查找,并提供了C++ STL中binary_search、upper_bound及lower_bound函数的使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原来的做法

while(left<=right){
if(x>middle)
left = middle+1;
if(x<middle)
right = middle - 1
if(x == middle)
return middle;
}
else return -1

朴素的二分可以保证每回循环索引前进或者后退,保证不会死循环,所以可以用循环条件left<=right

改进:不return middle,每次都夹到只有一个元素

while(left<right-1 )
middle = (left+right)/2;
if(a[middle]>=x)
right = middle; //维护的性质a[right]>=x
else 
left = middle;//维护的性质a[left]<x

这样的结果:能够找到对于x,索引最小的位置;
但是因为以上过程没有等号,我们最后还需要一个等号判断找到的元素是否相等;

if(a[right] == x) return right;//不能只判断右边,这个性质不总是满足的,如果最后一次夹逼刚好left=right-1的时候,这个性质没有被检验,但是已经跳出循环了
else if(a[left]==x) return left;
 return -1;

思考:上面的循环条件能不能改成这样?

left<right-1
left<right
left<=right
left<=right-1

left<=right 肯定不行
因为对于左边的情况,我们没有让索引前进,所以如果遇到``
x==a[midle]
left=middle;
如果
right=middle+1;`
这个时候死循环就出现了;
以后的middle 都等于left

死循环的条件
考察middle的产生过程
注意mid = (left + right) /2
mid = left +(right-left)
其实是等价的(考虑left,right分别为奇数偶数的过程)

middle = (left+right)/2
死循环代表middle = left或者right 的过程一直持续下去
这个时候,只可能是left和right相邻,由于整数除法向下取整,所以middle永远等于left,导致死循环

while(left < right -1)看成一种模板

考察这种情况下循环结束的条件
因为left<right-1因此middle一直不会和a[left]或者a[right]相等

当最后一次进入循环,left = right -2然后left或者right会移动到middle,然后left与right相邻,这个时候会跳出循环 也就是说:跳出循环的条件是left和right相邻

一般性的方法

我们在维持while(left < right -1)的前提下,考察a[left]a[right]的性质

对于朴素的二分法
朴素的二分法其实是第一类二分法(找到满足x的最小下标)和第二类二分法(找到满足x的最大下标)的特殊情况

考察第一类二分法
第一类二分法需要找到满足x的最小下标
我们可以通过维护a[left]a[right]的性质来实现
通过使得a[right]>=x a[left]<x
(为什么这样?因为a[right]>=x 我们要找最小的时候,要往左找,找到最左边的a[i])
当跳出循环的时候a[left]<x只有a[right]可能满足x作为最小下标
所以我们判断
if(a[right] == x ) return right;
else return -1
完整的代码实现

 while(left < right -1){
    middle = ( right + left )/2;
    if( a[middle]>= x ) right = middle;   
    else  left = middle;
    }  
    if(a[right] == x) return right;
    else return -1;

考察第二类二分法
由于第二类二分法需要找到最大的满足的下标
所以需要a[left]<=x a[right]>x

“`
while(left < right -1){
middle = (left+right)/2;
if(a[middle] <= x) left = middle;
else right = middle;
}
if(a[left] == x) return left;
else return -1;


####对于朴素的二分法,选择以上一种就可以了




STL
---

C++ STL 二分查找函数
binary_search
这个函数的返回值是布尔型,也就是最简单的找到了就为真,没找到就是假。

传入参数有三个,数据集合的左端点,数据集合的右端点,查找的值。

注意这些左端点右端点是要求左开右闭原则的,就是和数学上的左开右闭区间[a, b)一样,右端点是个不会被查阅的值。

一般来说写法类似:

bool flag = false;
int data[n] = {...};///数据
sort(data, data + n);
flag = binary_search(data, data + n, val);
cout << flag << endl;
vector<int> datav(data, data + n);
sort(datav.begin(), datav.end());
flag = false;
flag = binary_search(datav.begin(), datav.end(), val);
cout << flag << endl;
注意所有的数据集合必须是有序的,否则二分查找就没意义了。

upper_bound & lower_bound
这两个函数的返回值是地址或迭代器,也就是标准库中的容器iterator。当然,实际运用中必须用返回值减去集合首地址才能正常的获取我们习惯的数组下标。如果没有检索到查找元,返回数据集合的首地址。参数什么的和binary_search一样。额外注意的是upper_bound返回的是下标真实值+1,换句话说,一个集合a = {1, 2, 3, 4, 4, 4, 5, 6, 7, 8},如果数组下表从0开始记录,那么upper_bound(a, a + 10, 4)的返回值是第七个元素的地址。但是实际上第六个元素是最后一个4。

具体的函数使用可以看下面的代码示例

代码示例
/****
    *@author    Shen
    *@title     Test STL Binary Search Function
    */

#include <iostream>
#include <algorithm>
using namespace std;

typedef long long int64;

int main()
{
    int build[10] = {1, 2, 3, 4, 4, 4, 5, 6, 7, 8};
    for (int i = 0; i < 10; i++)
        cout << "build[" << i << "] = " << build[i] << endl;
    cout << upper_bound(build, build + 10, 4) - build << endl;///6
    cout << lower_bound(build, build + 10, 4) - build << endl;///3
    cout << lower_bound(build, build + 10, 0) - build << endl;///0
    cout << upper_bound(build, build + 10, 0) - build << endl;///0
    cout << binary_search(build, build + 10, 4) << endl;///1
    cout << binary_search(build, build + 10, 9) << endl;///0
    return 0;
}


stl 二分
1.binary_search(a,a+n,val);
返回布尔值
2.upper_bound(a,a+n,val);
lower_bound(a,a+n,val);
返回的是数组或者迭代器
应该减去首地址才能得到下标
常用upper_bound(a,a+n,val)-a
并且upper_bound返回的是真实值(从0开始计数)的位置+1(所以是upper_bound)
(保证找到的upper_bound是严格大于我们要找的元素)
3.如果没有找到,我的代码是返回-1
而stl会返回最小的下界,比如对于一个全正数的数组
那么stl会返回0,而我的代码返回-1
如果stl找不到upperbound,就会返回尾后元素
(stl和我的代码返回的都是从0开始的计数)

2016.7.6更新

int MyBinarySearch(int a[],int lo,int hi,int val){
    //左闭右开区间,val为要寻找的值
    int left=lo;
    int right=hi-1;
    int middle;
    while(left<=right){
        middle=(right+left)/2;
        if(val<a[middle]) right=middle-1;
        else if(val>a[middle]) left=middle+1;
        else return middle;
    }
    return -1;
}


//技巧:考虑最左边和最右边


//返回最大的i使得val>=P[i] 因为是最大,所以再往右一格就不行了
//(不存在最小的i使得val>=p[i],如果存在,就在头部)
//所以P[right]>val
int Binary_find_Upper(int P[],int size,int val){
    int left=0;
    int right=size-1;
    while(left<right-1){
        int middle=(left+right)/2;
        if(val>=P[middle]){
            left=middle;
        }
        else right=middle;
    }//a[left]<= val <a[right]
    if(val>=P[right]) return right;
    return left; 
}//如果val很小或者val==P[0],最后left=0,right=1,应该返回0
//如果val很大,最后left=size-2,right=size-1,但是应该返回size-1;

//返回最小的i使得val<=P[i]
//因为最小,所以再往左一格就不行了,所以是val>P[left]
//p[right]>=val
int Binary_find_Lower(int P[],int size,int val){
    int left=0;
    int right=size-1;
    while(left<right-1){
        int middle=(left+right)/2;
        if(val<=P[middle]) right=middle;
        else left=middle;
    }
    if(val<=P[left]) return left;
    return right;
}
//如果val很大,那么right=size-1,left=size-2 应该返回right
//如果val很小,left=0,right=1,应该返回left
//所有的判断条件只有一个原则:不满足我们所要求维护的性质的时候加上判断
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值