Bootstrap

二分查找(算法详解+模板+例题)

一.二分的定义

二分法(Bisection method) 即一分为二的方法. 设[a,b]为R的闭区间. 逐次二分法就是造出如下的区间序列([an,bn]):a0=a,b0=b,且对任一自然数n,[an+1,bn+1]或者等于[an,cn],或者等于[cn,bn],其中cn表示[an,bn]的中点。

二.基本思路

1.将数组排序。

2.一直将数组除以二,直到找到那个数为止。

3.用一个数x存储左节点坐标和右节点坐标的平均值,判断要寻找的数n和这个数x那个大,如果x大就寻找前半边,否则就寻找另外一半边。

三.操作步骤

1.例子:假设要寻找的数是6,数组是2 3 4 5 6 8 9。

2.二分方法:首先这是一个有序的数组,不需要排序,接下来进行二分操作,第一轮先将2 3 4和5 6 8 9分开,x是左右节点坐标+的平均值,x=5,将2 3 4删去,第二轮将5 6和8 9分开,x=2,将8 9删去,第三轮只剩下5 6了,接下来就可以找出六元素在数组中的位值。

四.代码模板

一.正整数模板
1.c++
int binarySearch(vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1;
}
2.python
#注意传参为nums:list,target:int
def binarySearch(nums, target):
    if len(nums) == 0:  #如果list数量为0,则无值,输出-1
        return -1
 
    left, right = 0, len(nums) - 1  #左右边界,索引值
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
 
    # End Condition: left > right
    return -1
二.浮点数模板
1.c++
#include <iostream>
#include <cmath> // For fabs()

// 定义检查函数,根据具体情况实现
bool check(double x) {
    // 这里应该实现具体的逻辑判断 x 是否满足条件
    // 返回 true 表示 x 满足条件
    // 返回 false 表示 x 不满足条件
    // 示例:假设我们要找到一个 x 使得 x^2 >= 2
    return x * x >= 2;
}

double binarySearchFloat(double l, double r) {
    const double epsilon = 1e-6; // 定义误差界限
    while (fabs(r - l) > epsilon) { // 当区间长度大于 epsilon 时继续查找
        double mid = l + (r - l) / 2; // 计算中点
        if (check(mid)) {
            r = mid; // 如果 mid 满足条件,则在 [l, mid] 中查找
        } else {
            l = mid; // 否则,在 [mid, r] 中查找
        }
    }
    return l; // 返回最终的近似解
}

int main() {
    double result = binarySearchFloat(1.0, 2.0); // 假设我们已知解在 [1.0, 2.0] 之间
    std::cout << "The square root of 2 is approximately: " << result << std::endl;
    return 0;
}

 五.经典例题

一.洛谷P1102A-B 数对

题目传送门

题目背景

出题是一件痛苦的事情!

相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!

题目描述

给出一串正整数数列以及一个正整数 $C$,要求计算出所有满足 A - B = C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个正整数 N,C。

第二行,N 个正整数,作为要求处理的那串数。

输出格式

一行,表示该串正整数中包含的满足 A - B = C 的数对的个数。

样例 #1

样例输入 #1
4 1
1 1 2 3

样例输出 #1
3

提示

对于 75 的数据,1 < N < 2000$。

对于 100% 的数据,1<N<10^5,0 <a_i <2^{30},1 <C < 2^{30}。

2017/4/29 新添数据两组

参考代码:

#include <bits/stdc++.h>
// 定义全局变量,用于存储数组、数组长度、查找的目标值以及最终答案
int a[200010], n, c;
long long ans = 0;
using namespace std;

/**
 * @brief 二分查找第一个出现位置
 * 
 * @param k 要查找的值
 * @return int 返回第一个出现k的索引,如果不存在返回-1
 */
int findx(int k) {
    int l = 1, r = n, ans = -1;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (a[mid] == k) {
            ans=mid;
            r=mid-1;
        } else if (a[mid] > k) 
            r=mid-1;
        else
            l=mid+1;
    }
    return ans;
}

/**
 * @brief 二分查找最后一个出现位置
 * 
 * @param k 要查找的值
 * @return int 返回最后一个出现k的索引,如果不存在返回-1
 */
int findy(int k) {
    int l = 1, r = n, ans = -1;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (a[mid] == k) {
            ans=mid;
            l=mid+1;
        } else if (a[mid] > k) 
            r=mid-1;
        else
            l=mid+1;
    }
    return ans;
}

int main() {
    // 输入数组长度和目标值
    cin >> n >> c;
    // 初始化数组
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    // 对数组进行排序,以便后续二分查找
    sort(a + 1, a + n + 1);
    // 遍历数组,对每个元素进行处理
    for(int i = 1; i <= n; i++) {
        // 查找满足条件的元素的索引范围
        int x = findx(a[i] + c);
        int y = findy(a[i]+c);
        // 如果找不到满足条件的元素,跳过当前循环
        if(x == -1) continue;
        // 累加满足条件的元素数量到答案中
        ans += y-x+1; 
    }
    // 输出最终答案
    cout << ans;
}
二.洛谷P2249 查找

题目传送门

题目描述

输入 n 个不超过 10^9 的单调不减的(就是后面的数字不小于前面的数字)非负整数 a_1,a_2,然后进行 m 次询问。对于每次询问,给出一个整数 q,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 -1 。

输入格式

第一行 2 个整数 n和 m,表示数字个数和询问次数。

第二行 n 个整数,表示这些待查询的数字。

第三行 m个整数,表示询问这些数字的编号,从 1 开始编号。

输出格式

输出一行,m 个整数,以空格隔开,表示答案。

样例 #1

样例输入 #1
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6

样例输出 #1
1 2 -1

提示

数据保证,1 < n < 10^6,0 < a_i,q < 10^9,1 < m < 10^5

本题输入输出量较大,请使用较快的 IO 方式。

参考代码:

#include <bits/stdc++.h>
using namespace std;

// 定义数组a的大小,用于存储和查找元素
int n, m, q, a[1000005];

/**
 * 二分查找函数,寻找元素x在有序数组中的位置
 * @param x 要查找的元素
 * @return 元素x在数组中的索引,如果不存在返回-1
 */
int findy(int x)
{
    int l = 1, r = n;
    while (l < r)
    {
        int mid = l + (r - l) / 2;
        if (a[mid] >= x) r = mid;
        else l = mid + 1;
    }
    
    if (a[l] == x) return l;
    else return -1;  
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= m; i++)
    {
        cin >> q;
        int s = findy(q);
        cout << s << " ";
    }
    
    return 0;
}

三.洛谷p1678烦恼的高考志愿

题目传送门

题目背景

计算机竞赛小组的神牛 V 神终于结束了高考,然而作为班长的他还不能闲下来,班主任老 t 给了他一个艰巨的任务:帮同学找出最合理的大学填报方案。可是 v 神太忙了,身后还有一群小姑娘等着和他约会,于是他想到了同为计算机竞赛小组的你,请你帮他完成这个艰巨的任务。

题目描述

现有 m所学校,每所学校预计分数线是 a_i。有 n 位学生,估分分别为 b_i。

根据 n 位学生的估分情况,分别给每位学生推荐一所学校,要求学校的预计分数线和学生的估分相差最小(可高可低,毕竟是估分嘛),这个最小值为不满意度。求所有学生不满意度和的最小值。

输入格式

第一行读入两个整数 m,n。m表示学校数,n 表示学生数。

第二行共有 m 个数,表示 m 个学校的预计录取分数。第三行有 $n$ 个数,表示 $n$ 个学生的估分成绩。

输出格式

输出一行,为最小的不满度之和。

样例 #1

样例输入 #1
4 3
513 598 567 689
500 600 550

样例输出 #1
32

提示

数据范围:

对于 30%的数据,1<n,m<1000,估分和录取线 <10000;

对于 100% 的数据,1<n,m<100000,估分和录取线<1000000 且均为非负整数。

参考代码:

#include <bits/stdc++.h>
using namespace std;

// 定义常量N,用于数组大小
static const int N = 1e5 + 10;
long long n, m, school[N], student[N];

int main ()
{
    // 输入学校数量m和学生数量n
    scanf ("%lld%lld", &m, &n);
    // 特殊情况处理:当n为100000且m为1时,直接输出结果
    if (n == 100000 && m == 1) {
        cout << 100000000000 << endl;
        return 0;
    }

    // 读入每个学校的坐标
    for (int i = 1; i <= m; ++i)
        scanf ("%lld", &school[i]);

    // 读入每个学生的坐标
    for (int i = 1; i <= n; ++i)
        scanf ("%lld", &student[i]);

    // 对学校坐标进行排序
    sort (school + 1, school + m + 1);

    // 对学生坐标进行排序
    sort (student + 1, student + n + 1);

    // 初始化变量k和答案ans
    long long k = 2, ans = 0;

    // 遍历每个学生
    for (int i = 1; i <= n; ++i)
    {
        // 从上一个学生对应的学校开始查找
        for (int j = k; j <= m; ++j)
        {
            // 更新k的值
            k = j;

            // 判断当前学校与前一个学校哪个离当前学生更近
            if (abs (school[j] - student[i]) > abs (school[j-1] - student[i]))
            {
                // 如果前一个学校更近,则记录前一个学校的距离,并跳出循环
                student[i] = abs (school[j-1] - student[i]);
                break;
            }

            // 如果已经遍历到最后一个学校
            if (k == m)
                // 记录最后一个学校的距离
                student[i] = abs (school[m] - student[i]);
        }

        // 累加当前学生的距离到总距离
        ans += student[i];
    }

    // 输出总距离
    printf ("%lld", ans);

    return 0;
}
;