C++知识精讲14 | 算法篇之二分查找算法

news2024/12/27 12:31:26

博主主页:Cool Kid~Yu仙笙_C++领域博主🦄

目录

 二分查找定义

 二分查找效率

二分查找与遍历的对比

二分查找的限制性

 二分查找的限制性(总结)

二分查找搭建 

循环实现二分查找

循环二分查找基本框架:

循环二分查找源码:

递归实现二分查找

递归二分查找基本框架:

递归二分查找源码:

C++二分查找例题【小试牛刀】 

一元三次方程求解

解析:

源码:

 C++二分查找例题【步入神坛】

字符串

解析:【此题非一般人可看(通俗说就是不全是二分查找的问题,跑题了)】

源码:

 二分查找定义

二分查找又称折半查找(Binary Search),优点是比较次数少,查找速度快,平均性能好,占用系统内存较少;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

 二分查找效率

二分查找是啥上面已经说明,现在我们来了解二分查找的效率

二分查找的时间复杂程度是O(logN)

O(logN) 查找速度有多快呢?我们来实验一下。

---------------------------------------------------------------------------------------------------------------------------------

我们假设数据大小是 n,每次查找后数据都会缩小为原来的一半,也就是会除以 2。最坏情况下,直到查找区间被缩小为空,才停止。

就因为这种特性,有的时候甚至比时间复杂度是常量级 O(1) 的算法还要高效。为什么这么说呢?

因为 logn 是一个非常“恐怖”的数量级,即便 n 非常非常大,对应的 logn 也很小。比如 n 等于 2 的 32 次方,这个数很大了吧?大约是 42 亿。也就是说,如果我们在 42 亿个数据中用二分查找一个数据,最多需要比较 32 次。

---------------------------------------------------------------------------------------------------------------------------------

二分查找与遍历的对比

上面也是讲述了二分查找的效率,不够直观,我们可以将二分查找与遍历进行一个对比,就能直观的看出二分查找在速度方面上的优势了

图片来源网络

从图中可以看出二分查找用了三步就找到了查找值,而遍历则用了11步才找到查找值,可见二分查找的效率非常之高。 

---------------------------------------------------------------------------------------------------------------------------------

刚刚我们对比讲述了一下二分查找的优点,现在讲讲二分查找的局限(该如何正确选择使用二分查找)

二分查找的限制性

1. 二分查找需要利用下标随机访问元素,如果我们想使用链表等其他数据结构则无法实现二分查找。这就说明,二分查找依赖数组结构

-----------------------------------------------------------------------------------------------------------------------

2.二分查找需要的数据必须是有序的。如果数据没有序,我们需要先排序,排序的时间复杂度最低是 O(nlogn)。所以,如果我们针对的是一组静态的数据,没有频繁地插入、删除,我们可以进行一次排序,多次二分查找。这样排序的成本可被均摊,二分查找的边际成本就会比较低。但是,如果我们的数据集合有频繁的插入和删除操作,要想用二分查找,要么每次插入、删除操作之后保证数据仍然有序,要么在每次二分查找之前都先进行排序。针对这种动态数据集合,无论哪种方法,维护有序的成本都是很高的。所以,二分查找只能用在插入、删除操作不频繁,一次排序多次查找的场景中。针对动态变化的数据集合,二分查找将不再适用,这就说明了,二分查找针对的是有序数据

-------------------------------------------------------------------------------------------------------------------------

3.如果要处理的数据量很小,完全没有必要用二分查找,顺序遍历就足够了。比如我们在一个大小为 10 的数组中查找一个元素,不管用二分查找还是顺序遍历,查找速度都差不多,只有数据量比较大的时候,二分查找的优势才会比较明显。,这就说明了,二分查找不适合数据量太小

-------------------------------------------------------------------------------------------------------------------------

4.二分查找底层依赖的是数组,数组需要的是一段连续的存储空间,所以我们的数据比较大时,比如1GB,这时候可能不太适合使用二分查找,因为我们的内存都是离散的,电脑内存可能没有那么多。这就说明了,数据量太大不适合二分查找

-------------------------------------------------------------------------------------------------------------------------

 二分查找的限制性(总结)

  • 二分查找依赖数组结构
  • 二分查找针对的是有序数据
  • 二分查找不适合数据量太小
  • 数据量太大不适合二分查找

二分查找搭建 

二分查找有两种实现方式,接下来依次展开说明

  • 循环
  • 递归

循环实现二分查找

循环二分查找基本框架:

int BinarySearch(int arr[], int len, int target) {
	int low = 0;
	int high = len;
	int mid = 0;
	while (low <= high) {
		mid = (low + high) / 2;
		if (target < arr[mid]) high = mid - 1;
		else if (target > arr[mid]) low = mid + 1;
		else return mid;
	}
	return -1;
}

循环二分查找源码:

#include<iostream>
using namespace std;
 
int BinarySearch(int arr[], int len, int target) {
	int low = 0;
	int high = len;
	int mid = 0;
	while (low <= high) {
		mid = (low + high) / 2;
		if (target < arr[mid]) high = mid - 1;
		else if (target > arr[mid]) low = mid + 1;
		else return mid;
	}
	return -1;
}
int main() {
	int array[] = { 7,14,18,21,23,29,31,5,38,42,46,49,52 };
	int arrayLen = sizeof(array) / sizeof(array[0]);
	int index = BinarySearch(array, arrayLen, 52);
	if (index != -1) {
		cout << "查找" << array[index] << "成功";
	}
	else {
		cout << "查找失败";
	}
	return 0;
}

递归实现二分查找

递归二分查找基本框架:

int BinarySearch(int arr[], int low, int high, int target) {
	if (low > high)return -1;
	else {
		int mid = (low + high) / 2;
		if (target < arr[mid]) return BinarySearch(arr, low, mid - 1, target);
		else if (target > arr[mid]) return BinarySearch(arr, mid + 1, high, target);
		else return mid;
	}
}

递归二分查找源码:

#include<iostream>
using namespace std;
 
int BinarySearch(int arr[], int low, int high, int target) {
	if (low > high)return -1;
	else {
		int mid = (low + high) / 2;
		if (target < arr[mid]) return BinarySearch(arr, low, mid - 1, target);
		else if (target > arr[mid]) return BinarySearch(arr, mid + 1, high, target);
		else return mid;
	}
}
int main() {
	int array[] = { 7,14,18,21,23,29,31,5,38,42,46,49,52 };
	int arrayLen = sizeof(array) / sizeof(array[0]);
	int index = BinarySearch(array, 0, arrayLen, 52);
	if (index != -1) {
		cout << "查找" << array[index] << "成功";
	}
	else {
		cout << "查找失败";
	}
	return 0;
}

现在,大家或多或少已经了解二分查找以及使用了,那么现在,我们就可以去小试牛刀了

---------------------------------------------------------------------------------------------------------------------------------

C++二分查找例题【小试牛刀】 

一元三次方程求解

解析:

这道题符合了运用二分查找的四个条件,所以直接运用即可,难度低

源码:

#include<cstdio>
double a,b,c,d;
double fc(double x)
{
    return a*x*x*x+b*x*x+c*x+d;
}
int main()
{
    double l,r,m,x1,x2;
    int s=0,i;
    scanf("%lf%lf%lf%lf",&a,&b,&c,&d);  //输入
    for (i=-100;i<100;i++)
    {
        l=i; 
        r=i+1;
        x1=fc(l); 
        x2=fc(r);
        if(!x1) 
        {
            printf("%.2lf ",l); 
            s++;
        }      //判断左端点,是零点直接输出。
                        
                        //不能判断右端点,会重复。
        if(x1*x2<0)                             //区间内有根。
        {
            while(r-l>=0.001)                     //二分控制精度。
            {
                m=(l+r)/2;  //middle
                if(fc(m)*fc(r)<=0) 
                   l=m; 
                else 
                   r=m;   //计算中点处函数值缩小区间。
            }
            printf("%.2lf ",r);  
            //输出右端点。
            s++;
        }
        if (s==3) 
            break;             
            //找到三个就退出大概会省一点时间
    }
    return 0;
}

相信经过这一道题的练手,大家都能熟练运用了,那么接下来,就让我们进入在二分查找自由翱翔的进阶吧

 C++二分查找例题【步入神坛】

字符串

解析:【此题非一般人可看(通俗说就是不全是二分查找的问题,跑题了)】

设我们的答案为mid(注意这里有坑是[a,b]的所有子串和[c,d]这个子串的最长lcp),那么我们会发现一个很有趣的事实: 如果mid可行的话,那么任意一个比mid小的数也可行

也就是说,问题满足可二分性,那么我们可以二分答案,将原问题转化为一个判定性问题:mid这个答案行不行?

那么我们发现,如果mid这个答案可以的话,就会存在一个后缀S,

1.它的开头在[a,b-mid+1]当中。

2.lcp(S,c)>=mid。

再次转化一步,就是询问满足以上两个条件的后缀S的个数,经典的二元限制统计问题,我们的思路很简单,摁死一个再去管下一个,发现一件有趣的事实:如果把这些后缀排好序,那么lcp符合要求的一定是一段连续的区间,(为什么?,因为我们发现排好序以后,lcp这个函数是单峰的,并且峰值在自己这里)

那么我们似乎可以二分左端点和右端点,为此我们需要O(1)求出区间最小值,为此我们还得写一个St表QAQ

那么最后我们发现现在两个限制都是区间型的了,而且是静态区间,没有修改,所以可以用主席树查询一发……

源码:

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int N=100010;int n;int m;
char mde[N];int sa[N];int rk[2*N];int ht[N];
int x[N];int y[N];queue <int> q[N];
inline bool cmp(int i,int j){return (x[i]==x[j])&&(y[i]==y[j]);}
inline void rixs()//这里的后缀数组用的是队列实现,常数较大
{
    for(int i=1;i<=n;i++){q[y[i]].push(i);}
    int cnt=0;for(int i=0;i<=n;i++)
    {for(;!q[i].empty();q[i].pop()){sa[++cnt]=q[i].front();}}
    for(int i=1;i<=n;i++){q[x[sa[i]]].push(sa[i]);}
    cnt=0;for(int i=0;i<=n;i++)
    {for(;!q[i].empty();q[i].pop()){sa[++cnt]=q[i].front();}}
    rk[sa[1]]=1;for(int i=2;i<=n;i++)
    {rk[sa[i]]=(cmp(sa[i-1],sa[i]))?rk[sa[i-1]]:i;}
}
inline void create_sa()//板子啥的问度娘吧
{
    for(int i=1;i<=n;i++){q[mde[i]-'a'+1].push(i);}
    int cnt=0;for(int i=1;i<=26;i++)
    {for(;!q[i].empty();q[i].pop()){sa[++cnt]=q[i].front();}}
    rk[sa[1]]=1;for(int i=2;i<=n;i++)
    {rk[sa[i]]=(mde[sa[i-1]]==mde[sa[i]])?rk[sa[i-1]]:i;}
    for(int k=1;k<=n;k*=2)
    {for(int i=1;i<=n;i++){x[i]=rk[i];y[i]=rk[i+k];}rixs();}
}
inline void calch()
{
    int j=0;int k=0;for(int i=1;i<=n;ht[rk[i++]]=k)
    {for(k=k?k-1:k,j=sa[rk[i]-1];mde[i+k]==mde[j+k];k++);}
}
int st[22][N];int log[N];
inline void calclog()//打表log,方便使用
{int i=0;for(int j=1;j<=n;j++){if((1<<(i+1))<=j)i++;log[j]=i;}}
inline void create_st()//对ht建st表
{
    for(int i=0;i<=n-1;i++){st[0][i]=ht[i+1];}
    for(int i=1;i<=log[n];i++)
    {for(int j=0;j<n-(1<<(i-1));j++){st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);}}
}
inline int rmq(int l,int r)//左开右闭的rmq
{int len=r-l;int res=min(st[log[len]][l],st[log[len]][r-(1<<log[len])]);return res;}
struct per_linetree//主席树的板子,这个真的是纯板子了
{
    int s[2][44*N];int fa[44*N];int root[N];int cnt;int val[44*N];
    per_linetree(){root[0]=1;cnt=1;}
    inline void insert(int p1,int p2,int l,int r,int pos)
    {
        val[p2]=val[p1]+1;if(r-l==1)return;int mid=(l+r)/2;
        if(pos<=mid){s[0][p2]=++cnt;s[1][p2]=s[1][p1];insert(s[0][p1],cnt,l,mid,pos);}
        else {s[1][p2]=++cnt;s[0][p2]=s[0][p1];insert(s[1][p1],cnt,mid,r,pos);}
    }
    inline void add(int t1,int t2,int pos)
    {root[t2]=++cnt;insert(root[t1],root[t2],0,n,pos);}
    inline int sum(int p1,int p2,int l,int r,int dl,int dr)
    {
        if(dl==l&&dr==r){return val[p2]-val[p1];}int mid=(l+r)/2;int res=0;
        if(dl<mid)res+=sum(s[0][p1],s[0][p2],l,mid,dl,min(dr,mid));
        if(mid<dr)res+=sum(s[1][p1],s[1][p2],mid,r,max(dl,mid),dr);
        return res;
    }
    inline int query(int t1,int t2,int l,int r)
    {return sum(root[t1-1],root[t2],0,n,l-1,r);}
}plt;
inline bool jud(int x,int a,int b,int c)//检测mid是否可行
{
    int l=1;int r=rk[c];int up;int down;//二分上边界,注意是左开右闭
    while(l<r){int mid=(l+r)/2;if(rmq(mid,rk[c])<x){l=mid+1;}else {r=mid;}}
    up=r;
    l=rk[c];r=n;//二分下边界
    while(l<r){int mid=(l+r+1)/2;if(rmq(rk[c],mid)<x){r=mid-1;} else{l=mid;}}
    down=r;
    return plt.query(up,down,a,b-x+1)!=0;//主席树查一发是否存在符合要求的后缀
}
inline int solve(int a,int b,int c,int d)//主二分过程
{
    int l=0;int r=min(b-a+1,d-c+1);//这个就是裸的二分答案了
    while(l<r){int mid=(l+r+1)/2;if(jud(mid,a,b,c)){l=mid;}else {r=mid-1;}}
    return r;
}
int main()
{
    scanf("%d%d",&n,&m);scanf("%s",mde+1);
    create_sa();calch();calclog();create_st();//上来先预处理
    for(int i=1;i<=n;i++){plt.add(i-1,i,sa[i]);}//对sa建主席树
    for(int i=1;i<=m;i++)
    {
        int a;int b;int c;int d;
        scanf("%d%d%d%d",&a,&b,&c,&d);
        printf("%d\n",solve(a,b,c,d));
    }return 0;//拿下!
}

还有更多精彩内容聚焦在C++知识精讲专栏【记得订阅哦】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2189.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【苹果家庭iMessage推送】Aupperpushslcertificate或ProductPushsCertificate证书不可以过期

推荐内容IMESSGAE相关 作者推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者推荐内容3.日历推 *** 点击即可查看作者要求内容信息作者推荐…

前端实现给文字添加动态背景

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

【JAVA开发】提高开发效率的工具分享

代码管理工具 仓库&#xff1a; GitHub or GitLab or 本地仓库 版本控制&#xff1a; git or svn 推荐gitLabgit 多分支敏捷开发 开发工具 IDEA 最方便开发工具了 当然如何你是全栈也可以考虑使用VS(visual studio)、HBuider、AS(android studio) 文本工具 Sublime text …

Redis数据结构之整数集合

目录 基本数据结构 例子 升级 升级之后新元素的摆放位置 好处 降级 整数集合可以理解为一个有序&#xff08;升序&#xff09;的不允许元素重复的数组。 基本数据结构 intset会根据 编码格式分配空间。 例子 升级 当新添加的元素超过了当前编码格式所能 表示的范围&…

Linux常用命令工具

1、查找特定文本中的特定字符 cat filename | grep myStr eg: cat .config | grep KCOV 2、查找特定文本中的特定字符并打印具体行数 cat filename | grep -n myStr eg:: cat .config | grep -n KCOV 3、查找一个文件夹中的特定字符 grep -r myStr filedir eg: grep -r __NR_…

Mysql注入

&#x1f4aa;&#x1f4aa;mysql注入前言1.mysql之union注入1.1.判断是否有注入&#xff1a;1.2.信息收集1.3.进行注入1.4.完整注入实列2. mysql 跨库注入&#xff1a;2.1第一步就是找到数据库2.2第二步就是注入3.自己写union注入4.其他注入操作前言 &#x1f48e;&#x1f48…

JVM笔记:垃圾回收及垃圾回收算法

垃圾是指在运行程序中没有任何指针指向的对象&#xff0c;这个对象就是需要被回收的垃圾。如果不及时对内存中的垃圾进行清理&#xff0c;那么&#xff0c;这些垃圾对象所占的内存空间会一直保留到应用程序结束&#xff0c;被保留的空间无法被其他对象使用。甚至可能导致内存溢…

【Vue项目回顾】网络模块的封装

选择什么网络模块 选择一: 传统的Ajax是基于XMLHttpRequest(XHR) 为什么不用它呢? 非常好解释, 配置和调用方式等非常混乱.编码起来看起来就非常蛋疼.所以真实开发中很少直接使用, 而是使用jQuery-Ajax 选择二: 在前面的学习中, 我们经常会使用jQuery-Ajax 相对于传统的A…

【PyTorch深度学习项目实战100例】—— 基于Pytorch的语音情感识别系统 | 第71例

前言 大家好,我是阿光。 本专栏整理了《PyTorch深度学习项目实战100例》,内包含了各种不同的深度学习项目,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmPy…

C#和Python使用C++编译的dll

C代码如下: extern "C" __declspec(dllexport) int __stdcall add(int a, int b) {return a b; } 因为是显示链接&#xff0c;所以只需要获得生成的dll即可 因为C#无法直接调用C的dll&#xff0c;所以我们使用了extern"C" 使用vs自带的工具x86_x64 Cross…

Vue中KeepAlive 原理与源码分析

概念 keep-alive是Vue的一个内置实例&#xff0c;用于缓存组件。当keep-alive包裹动态组件时&#xff0c;会缓存不活动的组件实例&#xff0c;而不是摧毁它们。keep-alive 是一个抽象组件&#xff1a;它自身不会渲染一个 DOM 元素&#xff0c;也不会出现在父组件链之中。如需要…

网课查题API接口-搜题api

网课查题API接口-搜题api 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#x…

小学生python游戏开发pygame--设置内容整理

游戏开发&#xff0c;相关设置内容单独放在一个文件中 如长宽&#xff0c;大小&#xff0c;颜色等起名shezhi.py&#xff0c;如下&#xff1a; # _*_ coding: UTF-8 _*_ # 开发团队&#xff1a; 信息化未来 # 开发人员&#xff1a; Administrator # 开发时间&#xff1a;2022…

微信小程序中如何实现省市区街道四级地址级联选择

大家好&#xff0c;我是雄雄。 前言 微信小程序中支持省市区地址级联吗&#xff1f;微信小程序中的地址级联最多支持到几级&#xff1f; 今天&#xff0c;我们就来看看&#xff0c;微信小程序中的地址级联的使用&#xff0c;以及一些坑…希望大家看完之后能避免踩坑啊。 省市…

JS(受人以鱼 不如受人以渔)第十七课

比你优秀的人都比你努力&#xff0c;你有什么理由不去努力。基础来自己的累秒累天累月的积累 没有一个人是从天而降的天才&#xff0c;也没有哪个人想做一个一生贫庸的人。今天我想说 受人以鱼 不如受人以渔 推荐几个官网可以自己主动去学习 JavaScript中文网-JavaScript教程…

虚拟物品(游戏)交易平台的设计与实现(Java+SSM+MySQL)

目 录 摘 要 I Abstract II 第1章 绪论 1 1.1选题背景及意义 1 1.2研究现状 1 1.3研究主要内容 2 第2章 相关技术介绍 4 1.1 SSM的技术原理 4 1.1.1 SSM语言及其特点 4 1.1.2 Java及Java Servlets概述 6 1.1.3 JavaBean简介 6 1.2 服务器配置 7 1.2.1 Tomcat安装及配置 7 1.2.2…

工业控制系统所面临的安全威胁

年新增漏洞严重程度分析 因本文收集处理的公开漏洞基本上都被 CVE 所收录&#xff0c; 所以本文在分析这些漏洞的严重 性时&#xff0c;将主要根据 CVE 的 CVSS 评估值①来判断&#xff0c;并划分为高、中、低三种情况。 根据图 2.9 的统计分析 &#xff0c;2013 年的新增漏洞…

MySQL索引优化

索引优化 EXPLAIN查询分析 参考另一篇&#xff0c;通过explain我们可以确定查询语句的访问类型&#xff0c;用到的索引&#xff0c;扫描行数以及extra信息。 回表查询与覆盖索引 InnoDB索引有聚簇索引和辅助索引。聚簇索引的叶子节点存储行记录&#xff0c;InnoDB必须要有&…

sql查询面试题复习

基础 查所有的数据 select * from user_profile;查询某几列 select device_id,gender,age,university from user_profile;取出学校的去重数据。 select distinct university from user_profile; #distinct 关键字查看前2个用户明细设备ID数据 select device_id from user_p…

反射、枚举和lambda表达式

文章目录一、反射反射的相关类获得Class对象的三种方法反射的基本使用反射优点和缺点二、枚举背景及定义枚举的使用为什么Enum源码中找不到values()方法&#xff1f;枚举与反射总结三、Lambda表达式函数式接口Lambda表达式的基本使用lambda在集合中的应用总结一、反射 Java的反…