整数二分算法和浮点数二分算法

整数二分算法和浮点数二分算法


二分

现实中运用到二分的就是猜数字的游戏 假如有A同学说B同学所说数的大小,B同学要在1~100中间猜中数字65,当B同学每次说的数都是范围的一半时这就算是一个二分查找的过程
在这里插入图片描述

二分查找的前提是这个数字序列要有单调性

基本步骤

初始化:

设定两个指针,left 和 right,分别指向数组的起始和末尾。

计算中间位置:

使用公式 mid = left + (right - left) / 2 或者left+right>>1计算中间位置。

比较:

如果中间位置的元素等于目标值,返回中间位置。
如果目标值小于中间位置的元素,则将 right 更新为 mid - 1,继续在左半部分查找。
如果目标值大于中间位置的元素,则将 left 更新为 mid + 1,继续在右半部分查找。

重复:

重复步骤 2 和 3,直到 left 超过 right。

结束:

如果在查找过程中未找到目标值,返回一个表示未找到的结果(如 -1 或 None)。

二分查找算法的时间复杂度是 O(log n),非常高效。

整数二分

二分的本质并不是一定要单调,而是对一个区间可以化分成两个部分,一部分一定满足条件,另一部分一定不满足,对于满足这种条件的我们可以找出两个边界点,这样的话二分算法可以寻找这个性质的边界(红色和黑色的边界都行,因为是整数二分所以两边界不重合)。
在这里插入图片描述

第一种情况(二分左半部分)

假如说有一串数字1,2,3,3,4,4,5,6,8,8我们需要找到满足小于等于3的最大情况的子序列,也就是我们需要找到最后一次出现的3,我们可以如何做呢?
我们让一个mid=(l+r+1)/2 假如说a[mid]<=3,此时if(check(mid))==true说明此时mid指向的值可能是答案,但是我们无法保证其后面还有没有答案是<=3的,所以此时应该是l=mid,假如说此时a[mid]>=3,if(check(mid))==false说明此时mid指向的是一定不满足条件的的那么此时应该是r=mid-1
在这里插入图片描述
我们继续不段重复以上操作直到l>=r时退出循环。

第二种情况(二分右半部分)

还是这个数字序列这次我们要找1,2,3,3,4,4,5,6,8,8中第一次出现3的位置,我们应该怎么做呢?
我们还是让一个mid=(l+r)/2 假如说a[mid]>=3,此时if(check(mid))==true说明此时mid指向的值可能是答案,但是我们无法保证其前面还有没有答案是>=3的,所以此时应该是r=mid,假如说此时a[mid]<=3,if(check(mid))==false说明此时mid指向的是一定不满足条件的的那么此时应该是l=mid+1

注意:第一种情况是(l+r+1)而不是(l+r)为为什么呢?

因为计算机的除法都是向下取整的所以就会出现问题,假如说此时l=r-1那么mid=(l+r)/2=(l+l+1)/2=l然后我们假如发现l还是满足条件的,那么此时就会陷入l=mid,mid=l的死循环

我们来写一道题

洛谷P2249
下面的代码是既有第一次出现,也有最后一次出现的,两种情况都写了。
代码如下:

#include <iostream>
using namespace std;

const int N = 1e5 + 10; // 定义数组的最大容量,数组a最多可以存放1e5个元素
int a[N], n, m; // 定义全局变量数组a,n为数组长度,m为查询次数

// check1函数用于检查a[mid]是否大于等于目标值x
bool check1(int mid, int x) {
    if (a[mid] >= x) {
        return true; // 如果a[mid]大于等于x,返回true,表示满足条件
    } else {
        return false; // 否则返回false,表示不满足条件
    }
}

// check2函数用于检查a[mid]是否小于等于目标值x
bool check2(int mid, int x) {
    if (a[mid] <= x) {
        return true; // 如果a[mid]小于等于x,返回true,表示满足条件
    } else {
        return false; // 否则返回false,表示不满足条件
    }
}

int main() {
    cin >> n >> m; // 输入数组的长度n和查询的次数m
    for (int i = 1; i <= n; i++) {
        cin >> a[i]; // 依次输入数组a的元素
    }

    while (m--) { // 对每一次查询进行处理,m次查询
        int x; // 定义要查询的目标值x
        cin >> x; // 输入目标值x
        
        // 首先进行二分查找,寻找第一个大于等于x的位置
        int l = 1, r = n; // 初始化左右边界,l是左边界,r是右边界
        while (l < r) { // 当左边界小于右边界时,继续二分查找
            int mid = (l + r) >> 1; // 计算中间位置mid
            if (check1(mid, x)) { // 如果a[mid]大于等于x
                r = mid; // 缩小右边界至mid
            } else {
                l = mid + 1; // 否则缩小左边界至mid+1
            }
        }

        // 查找完成后,检查a[l]是否等于目标值x
        if (a[l] == x) {
            cout << l << " "; // 如果a[l]等于x,输出位置l
        } else {
            cout << -1 << " "; // 如果a[l]不等于x,输出-1表示未找到
        }

        // 再进行一次二分查找,寻找最后一个小于等于x的位置
        l = 1, r = n; // 重新初始化左右边界
        while (l < r) {
            int mid = (l + r + 1) >> 1; // 计算中间位置mid,向上取整
            if (check2(mid, x)) { // 如果a[mid]小于等于x
                l = mid; // 缩小左边界至mid
            } else {
                r = mid - 1; // 否则缩小右边界至mid-1
            }
        }

        // 查找完成后,再次检查a[l]是否等于目标值x
        if (a[l] == x) {
            cout << l << " "; // 如果a[l]等于x,输出找到的最后位置l
        } else {
            cout << -1 << " "; // 如果没找到,输出-1
        }
        cout<<endl;
    }
}

整数二分模板

bool check(int x) {
    // 这里是判断x是否满足某种性质的函数,具体实现取决于实际问题
    // 可以根据x的值来返回true或false,用于二分查找中的判断
    /* ... */
}

// 区间[l, r]被划分为[l, mid]和[mid + 1, r]时使用的二分查找
int bsearch_1(int l, int r) {
    // 二分查找的目的是在区间[l, r]中寻找满足某种性质的最小位置
    // l 是左边界,r 是右边界,最终返回满足性质的最小下标
    while (l < r) { // 当左边界小于右边界时,继续进行二分查找
        int mid = (l + r) >> 1; // 计算中间位置mid,使用右移操作进行快速计算,相当于 (l + r) / 2
        if (check(mid)) { // 如果check(mid)为true,表示mid满足性质
            r = mid; // 将右边界缩小到mid,因为我们要找满足性质的最小位置
        } else { // 否则,mid不满足性质
            l = mid + 1; // 将左边界缩小到mid + 1,因为mid以及它左边的值都不满足条件
        }
    }
    return l; // 返回最终的左边界,此时l == r,且为满足性质的最小位置
}

// 区间[l, r]被划分为[l, mid - 1]和[mid, r]时使用的二分查找
int bsearch_2(int l, int r) {
    // 二分查找的目的是在区间[l, r]中寻找满足某种性质的最大位置
    // l 是左边界,r 是右边界,最终返回满足性质的最大下标
    while (l < r) { // 当左边界小于右边界时,继续进行二分查找
        int mid = (l + r + 1) >> 1; // 计算中间位置mid,并向上取整,确保mid偏向右侧
        if (check(mid)) { // 如果check(mid)为true,表示mid满足性质
            l = mid; // 将左边界缩小到mid,因为我们要找满足性质的最大位置
        } else { // 否则,mid不满足性质
            r = mid - 1; // 将右边界缩小到mid - 1,因为mid以及它右边的值都不满足条件
        }
    }
    return l; // 返回最终的左边界,此时l == r,且为满足性质的最大位置
}

浮点数二分算法

浮点数二分相较于整数二分更加简单因为只有一个模板,并且没有边界问题,浮点数的二分查找可以用于求解需要精确值的问题,例如求方程的解或几何问题中涉及浮点精度的求解。与整数二分查找不同,浮点数二分查找必须考虑精度问题,因为浮点数无法精确到某个具体值,所以我们需要一个精度(如 epsilon),用于判断二分查找的终止条件。

假如说我们需要找一个数x的平方等于目标值2

代码如下:

#include <iostream>
#include <cmath> // 包含abs函数,用于计算绝对值
using namespace std;

// 定义一个需要使用二分法求解的函数,返回值为目标函数值
double f(double x) {
    // 举例:寻找函数 f(x) = x^2 - 2 的根
    return x * x - 2;
}

int main() {
    double l = 0, r = 2; // 初始区间[l, r],假设根位于[0, 2]之间
    double eps = 1e-7;   // 定义精度eps,即当结果误差小于1e-7时停止迭代

    // 当区间宽度大于精度要求时,继续二分
    while (r - l > eps) {
        double mid = (l + r) / 2; // 计算中间值
        if (f(mid) > 0) { // 如果 f(mid) > 0,表示 mid 处的值在根的右侧
            r = mid; // 缩小右边界至mid
        } else { // 否则 f(mid) <= 0,表示 mid 处的值在根的左侧或是根
            l = mid; // 缩小左边界至mid
        }
    }

    // 输出结果,l 和 r 最终都会逼近根
    cout << "x ≈ " << l << endl;
    cout << "验证结果: f(x) = " << f(l) << endl; // 输出验证f(l)接近0
}

浮点数二分模板

// 函数原型:在浮点数区间 [l, r] 上使用二分查找,找到满足某种性质的浮点数
double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 是精度控制的参数,当区间长度小于 eps 时停止二分查找
    // 1e-6 表示小数点后 6 位精度,可根据题目要求调整精度

    // 当区间长度大于 eps 时,继续进行二分查找
    while (r - l > eps)
    {
        // 计算区间的中点 mid
        double mid = (l + r) / 2;

        // 调用 check 函数判断 mid 是否满足给定的性质
        // 假设 check(mid) 返回 true,则意味着 mid 及其右侧可能满足性质,
        // 因此将右区间收缩到 mid,继续在左侧区间 [l, mid] 上搜索
        if (check(mid)) 
            r = mid;
        // 否则,mid 及其左侧不满足性质,因此我们将左区间收缩到 mid,继续在右侧区间 [mid, r] 上搜索
        else 
            l = mid;
    }

    // 最后返回左边界 l(或右边界 r),此时区间已经很小,接近于精度要求的结果
    // 因为 l 和 r 的距离非常小,最终答案应为 l 或 r 的近似值
    return l;
}

整数二分算法和浮点数二分算法源代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值