二分查找BinarySearch入门与实战(C++)

方便自己预习也帮大佬们复习

概述

概念
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
过程
首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
要求
1.必须采用顺序存储结构
2.必须按关键字大小有序排列


目前题型不多,会慢慢更新
下列代码均为AC代码,请放心食用

二分查找在查找数时是一种便捷高效的算法,在大规模查找中可以优化时间

库函数:
#include <algorithm>
1. lower_bound(序列首地址, 序列末地址, 查找对象):查找第一个>=查找对象的元素位置
2. upper_bound(序列首地址, 序列末地址, 查找对象):查找第一个>查找对象的位置
3. binary_search(序列首地址, 序列末地址, 查找对象):查找是否存在查找对象,返回truefalse

不推荐使用库函数查找,灵活性低,容易产生依赖性,但确定可以使用的时候为了方便还是可以用的

经典入门题

UVA10474测试链接

Where is the Marble?。

题意简述:
多实例
先输入n,q,以0 0结束
输入n个整数,代表大理石编号;
再输入q个数(编号),
问是否有这个编号的大理石,排完序后第一次出现的位置在哪里?

输入:
4 1
2 3 5 1
5
5 2
1 3 3 3 1
2 3
0 0

输出:
CASE# 1:
5 found at 4
CASE# 2:
2 not found
3 found at 3
.
------------------ 防止思考中看见代码------------------------------------------------------------------------------------------
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
int search(int *a, int num, int nsize) //*a代表数组a,num是要查找的数,nsize是a的长度
{
    
    
    int left = 0;
    int right = nsize - 1;
    while (left <= right)
    {
    
    
        int mid = left + ((right - left) >> 1); //位运算除二 更高效
        if (a[mid] == num)
            return 1;
        else if (a[mid] > num)
            right = mid - 1;
        else
            left = mid + 1;
    }
    return 0;
}
int searchFirst(int *a, int num, int nsize)//查最左边的数
{
    
    
    int left = 0, right = nsize - 1;
    while(left<=right)
    {
    
    
        int mid = left + ((right - left) >> 1); //位运算除二 更高效
        if (a[mid] >= num)
            right = mid - 1;
        else
            left = mid + 1;
    }
    return left;
}
int main()
{
    
    
    int n, m;
    int cas = 0;
    while (cin >> n >> m, n != 0 || m != 0)
    {
    
    
        cas++;
        int a[11000];
        for (int i = 0; i < n; i++)
            cin >> a[i];
        sort(a, a + n);
        int b[11000];
        printf("CASE# %d:\n", cas);
        for (int i = 0; i < m; i++)
        {
    
    
            cin >> b[i];
            if (!search(a, b[i], n))
                printf("%d not found\n", b[i]);
            else
                printf("%d found at %d\n", b[i], searchFirst(a, b[i], n)+1);
        } 
    }
    return 0;
}

简单二分题

1.一元二次方程求解

测试网站——牛客——链接

题目描述
给出n个整数和x,
请问这n个整数中是否存在三个数a,b,c
使得ax2+bx+c=0,
数字可以重复使用。

输入描述:
第一行两个整数n,x
第二行n个整数a[i]表示可以用的数
1 <= n <= 1000, -1000 <= a[i], x <= 1000

输出描述:
YES表示可以
NO表示不可以

输入
2 1
1 -2
输出
YES
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.暴力出a和b的值
2.在数组内查找是否存在-(a*x*x+b*x)
#include <bits/stdc++.h>
using namespace std;
int n,x;
int a[1100];
bool search(int num)//a已经在全局变量中给出,本函数为查找num是否存在
{
    
    
    int left=0,right=n-1;
    while(left<=right)
    {
    
    
        int mid=left+((right-left)>>1);
        if(a[mid]==num) return true;
        else if(a[mid]<num) left=mid+1;
        else right=mid-1;
    }
    return false;
}
int main()
{
    
    
    cin>>n>>x;
    for(int i=0;i<n;i++) cin>>a[i];
    sort(a,a+n);//二分之前切记排序
    for(int i=0;i<n;i++)
    {
    
    
        for(int j=0;j<n;j++)
        {
    
    
            int ans=a[i]*x*x+a[j]*x;//只用查找是否存在-ans
            if(search(-ans)) {
    
    printf("YES");return 0;}//小用法,单实例可直接用return 0;结束
        }
    }
    printf("NO");
    return 0;
}
2.一元三次方程求解

测试网站——牛客——链接

题目描述
有形如:ax3+bx2+cx+d=0 这样的一个一元三次方程。
给出该方程中各项的系数(a,b,c,d 均为实数),
并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值 ≥ 1。
要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。
提示:记方程f(x) = 0,若存在2个数x1和x2,且x1 < x2,f(x1)*f(x2) < 0,则在(x1,x2)之间一定有一个根。

输入描述:
一行,4个实数A,B,C,D。

输出描述:
一行,3个实根,并精确到小数点后2位。

输入
1 -5 -4 20
输出
-2.00 2.00 5.00
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.遍历出能让f(x)异号的两个x值
2.并在这两个x中查找所需要的那一个解
#include<bits/stdc++.h>
using namespace std;
double a,b,c,d;

double f(double x)
{
    
    
    return a*x*x*x+b*x*x+c*x+d;//计算一元三次方程
}
int main()
{
    
    
    int flag=0;//输出个数
    cin>>a>>b>>c>>d;
    for(double i=-99;i<=99;i++)
    {
    
    
        if(flag>=3) break;
        if(f(i)*f(i+1)<=0)//异号之间一定有接
        {
    
    
            if(f(i) == 0)        printf("%.2f ",i), flag++;
            else if(f(i+1) == 0) printf("%.2f ",i+1), i++, flag++;
            else{
    
    
                double left=i,right=i+1,mid=(left+right)/2;
                while(right-left>0.01)
                {
    
    
                    if(f(left)*f(mid)<=0) right=mid;
                    else                  left=mid;
                    mid=(left+right)/2;
                }
                printf("%.2f ",mid);
                flag++;
            }
        }
    }
    return 0;
}

思维二分题

1.在排序数组中查找元素第一个和最后一个位置

leetcode测试链接.

题目:给定一个按照升序排列的整数数组 nums,和一个目标值 target。
找出给定目标值在数组中的开始位置和结束位置。

PS:这是练习,尽量都思考如何将时间复杂度变成O(log n)

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

输入:nums = [], target = 0
输出:[-1,-1]
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.

思路:
1.一个往左找的二分
2.一个往右找的二分
class Solution {
    
    
public:
    vector<int> searchRange(vector<int>& nums, int target) {
    
    
        int len=nums.size();
        if(len==0) return {
    
    -1,-1};//优化时间1:建立第一个特判
        int left=0,right=len-1;
        int beginn,endd;
        while(left<=right)
        {
    
    
            int mid=left+((right-left)>>1);
            if(nums[mid]>=target) right=mid-1;//在内部也将右边界不断压缩
            else                  left=mid+1;
        }
        beginn=left;//被压缩的右边界超过了左侧第一个查找数,而此时的左边界便是左侧第一个数
        if(beginn>=len||beginn<0||nums[beginn]!=target) return {
    
    -1,-1};//优化时间2:拒绝不必要的数
        left=0,right=len-1;
        while(left<=right)
        {
    
    
            int mid=left+((right-left)>>1);
            if(nums[mid]<=target) left=mid+1;
            else                  right=mid-1;
        }
        endd=right;
        if(endd>=len||endd<0||nums[endd]!=target) return {
    
    -1,-1};//此半部分代码原理同上
        return {
    
    beginn,endd};//在特判完所有的之后输出
    }
};
2.寻找丑数

leetcode测试位置

题目:请你帮忙设计一个程序,用来找出第 n 个丑数。丑数是可以被 a 或 b 或 c 整除的 正整数。

输入:n = 3, a = 2, b = 3, c = 5
输出:4
解释:丑数序列为 2, 3, 4, 5, 6, 8, 9, 10… 其中第 3 个是 4。

输入:n = 4, a = 2, b = 3, c = 4
输出:6
解释:丑数序列为 2, 3, 4, 6, 8, 9, 10, 12… 其中第 4 个是 6。
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
//其实二分就是一种查数手段
//在大规模的数据中可以将所能想象到的组合设为二分主组
1.拿丑数的排列序号作为二分主组
2.拿mid.left.right做丑数在主组的位置进行二分即可
class Solution {
    
    
public:
    using ll=long long;
    ll LCM(ll a,ll b)//最小公倍数
    {
    
    
        return a*(b/__gcd(a,b));
    }
    //__gcd(,)是GNU编译器内algorithm的库函数,没事儿还是手打gcd(1.递归 2.位运算)吧,这个有时候不让用
    
    ll f(ll num,ll a,ll b,ll c,ll ab,ll ac,ll bc,ll abc)//判断num是满足题意的第几个丑数
    {
    
    
        return num/a+num/b+num/c-num/ab-num/ac-num/bc+num/abc;
    }
    //f函数原理自己拿几个数找一下规律即可
    ll nthUglyNumber(int n, int a, int b, int c) {
    
    
        ll ab,abc,ac,bc;
        ab=LCM(a,b);
        ac=LCM(a,c);
        bc=LCM(b,c);
        abc=LCM(a,bc);

        ll left=min(a,min(b,c)), right=2e9;//初始化边界
        while(left<=right)
        {
    
    
            ll mid=left+(right-left)/2;
            if(f(mid,a,b,c,ab,ac,bc,abc)<n) left=mid+1;//小于是一定小的
            else                            right=mid-1;//等于有可能大,大于一定大(3/2=2/2)),所以将 ">=" 放在一起
        }
        return left;
    }
};
3.思维题二分—Hamburgers

CodeForces-371C 的测试链接.

题目大意:
你需要制作很多汉堡,每个汉堡都需要面包(Bread)、芝士(Cheese)、香肠(Sausage)这三种材料来制作,目前你手里已有一部分材料和钱,你需要用你的钱去购买更多的材料,各种材料都不能被剪开,那么你最多能制作多少个汉堡呢?

输入:
.第一行一个字符串,代表制作一份汉堡所需要的每种材料个数,其中’S’代表一根香肠,'B’代表一片面包,'C’代表一片芝士。如:BCCSBSB则是代表了需要面包芝士芝士香肠面包香肠面包。
.第二行输入三个整数:nb,ns,nc(1<=nb,ns,nc<=100),分别代表了:已有的面包片数,香肠根数和芝士片数。
.第三行输入三个整数:pb,ps,pc(1<=pb,ps,pc<=100),分别代表一片面包的价钱,一根香肠的价钱和一片芝士的价钱。
.第四行一个整数money(1<=money<=10^12),代表你手中的资金

输出:
在购买后能制作出来的最大面包数

样例:
输入:
BBBSSC
6 4 1
1 2 3
4
输出:
2

输入:
BBC
1 10 1
1 10 1
21
输出:
7

输入:
BSC
1 1 1
1 1 3
1000000000000
输出:
200000000001
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.由于限制为手里的资金我们只需要拿买的汉堡的个数所需要的资金作为参考来进行二分内部的对比
2.与前几题中查最右边的数一样,压缩左边界便是我们需要的最大值
<难点>:二分的参考变量与计算购买至n个物品所需要的资金
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll maxn = 1e13;
ll neach_b, neach_s, neach_c;//一个汉堡需要的b,s,c的个数
ll nb, ns, nc;//现有b,s,c的个数
ll pb, ps, pc;//b,s,c各自需要的金额
ll money;//手里的资金
ll cost(ll num)//要带上原有的材料精准购买num个汉堡要的钱数
{
    
    
    return max((ll)0, neach_b * num - nb) * pb + max((ll)0, neach_s * num - ns) * ps + max((ll)0, neach_c * num - nc) * pc;
}
ll searchMake()//查找能买多少份汉堡原料
{
    
    
    ll left = 0, right = maxn;
    while(left<=right)
    {
    
    
        ll mid = left + ((right - left) >> 1);
        if(cost(mid)>money)
            right = mid - 1;
        else
            left = mid + 1;
    }
    if(right<0)
        return 0;
    return right;
}
/*这个二分函数思路与上题《在排序数组中查找元素第一个和最后一个位置》相同
不断压缩左边界求得最大值
*/
int main()
{
    
    
    neach_c = 0, neach_b = 0, neach_s = 0;
    string s;
    cin >> s;
    ll len = s.size();
    for (ll i = 0; i < len;i++)
    {
    
    
        if(s[i]=='B')
            neach_b++;
        else if(s[i]=='S')
            neach_s++;
        else if(s[i]=='C')
            neach_c++;
    }//统计制作一份汉堡所需原料数量
    cin >> nb >> ns >> nc >> pb >> ps >> pc >> money;
    cout << searchMake() << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/SnopzYz/article/details/112971835
今日推荐