【二分查找】【统计x出现的次数】【求方程的解】【循环控制二分查找的次数从而控制精度】【查找x第一次出现的位置】

本文详细介绍了二分查找的基本思路、可视化示例、C++实现、时间复杂度以及在不同场景的应用,如日期计算和字符串排序。此外,还探讨了精度提升导致的问题及解决方案,包括使用固定次数二分查找和处理方程解的精度问题。
摘要由CSDN通过智能技术生成

目录

【基本思路】

【可视化】

【算法实现】

【时间复杂度】

【简单题目示例】

【应用范围】

【统计x出现的次数】

【求方程的解】

 【精度问题的讨论】如果我们将精度提高到小数点后15位呢?

【查找x第一次出现的位置】


【基本思路】

        我们设定一个初始的L和R,保证答案[L,R]中,当[L,R]中不止有一个数字的时候,取区间的中点M,询问这个中点和答案的关系,来判断答案是M,还是位于[L,M-1],还是位于[M+1,R]

【可视化】

        提供一个二分查找的可视化图
Binary and Linear Search Visualization (usfca.edu)https://www.cs.usfca.edu/~galles/visualization/Search.html

【算法实现】

        从左向右找到第一个:

//把R不断的向左推
int L = 0;
	int R = n - 1;
	while (L < R)
	{
		int mid = L + (R - L) / 2;//这里要向下取整
		if (a[mid] < x)
			L = mid + 1;
		else
			R = mid;
	}

        从右向左找到第一个: 

//把L不断的向右推
int L = 0;
	int R = n - 1;
	while (L < R)
	{
		int mid = L + (R - L+1) / 2;//这里要向上取整
		if (a[mid] < x)
			L = mid ;
		else
			R = mid-1;
	}

【时间复杂度】

        每一次查找都会使区间的长度变为原来的一半,时间复杂度为:O(log n)

【简单题目示例】

        给出一个从小到大排列好的数组,要找到第一个大于等于 x 的数字并输出。

        输入n,x,以及数组a(已经从小到大排好序)

        输入示例:

9 4
2 3 3 3 3 4 4 4 4

        输出示例:

5

        采用代码如下:

#include<iostream>
using namespace std;
#define N 100000
int a[N], n, x;
int main()
{
	cin >> n >> x;
	for (int i = 0; i < n; i++)
		cin >> a[i];
	if (x > a[n - 1])
		cout << "fail" << endl;
	int L = 0;
	int R = n - 1;
	while (L < R)
	{
		int mid = L + (R - L) / 2;
		if (a[mid] < x)
			L = mid + 1;
		else
			R = mid;
	}
	if (a[L] == x)
		cout << L +1<< endl;//这里的L+1是考虑数组下标从0开始
	else
		cout << "fail" << endl;
}

【应用范围】

(1)要求数组是有序的;

(2)其他有序结构:

        【日期】将日期转化为YYYYMMDD的形式,利用(YYYYMMDD1+YYYYMMDD2)÷2 来找到两个日期的中点,如果遇到有月份不合法的日期,只需要向下取整到12月即可。

        【字符串】由于字符串具有天然的字典序,因此可以给一组字符串进行字典序排序。string类中的比较函数sort()为给字符串进行字典序排序

        【二维数据】先比较第一维,第一维相同时再比较第二维。确定第一维相同时的区间,即找到第一个大于等于第二维最小值的指针,最后一个小于等于第二维最大值的指针,两个指针相减即确定了区间。

【统计x出现的次数】

【题目描述】

        给出一个正整数 n,和一个长度为 n 的整数数组 a,再给出一个正整数 q,接下来给出 q个询问,每个询问包含一个整数 x,你需要输出 x 在数组 a 中出现了几次

【基本思路】

        利用二分查找从左向右和从右向左分别找到第一个大于等于 x 的指针最后一个大于等于 x 的指针。新学到了两个函数,因为二分查找实现起来没有那么容易,所以在C++的STL库中提供了两个函数,可以分别实现上述功能,以下两个函数的实现都是依赖于二分查找

     lower_bound(数组头指针,数组尾指针,待查找字符 x)——>返回第一个大于等于 x 的指针

     upper_bound(数组头指针,数组尾指针,待查找字符 x)——>返回最后一个大于等于 x 的指针

#include<algorithm>//头文件位置

        有关于两个函数的更详细介绍放在下面

【C++】从没见过这么详细的lower_bound的讲解_am brother的博客-CSDN博客_lower_boundhttps://blog.csdn.net/weixin_43939593/article/details/105602530采用代码如下:

#include<iostream>
#include<algorithm>
#define N 100000
using namespace std;
int n, q, x, a[N];
int main()
{
	cin >> n;
	for (int i = 0; i < n; i++)
		cin >> a[i];
	sort(a, a + n);//从小到大排序
	cin >> q;
	while (q--)
	{
		cin >> x;
		int *lp = lower_bound(a, a + n, x);
		int *rp = upper_bound(a, a + n, x);
		cout << int(rp - lp) << endl;//得到区间长度
	}
	return 0;
}

【求方程的解】

【题目描述】

        请输出方程x^{3} + 16 = 0的解,已知这个解在[-10^{9},10^{9}]之间,并且函数f(x) = x^{3} + 16在定义域上单调递增。输出的答案保留5位小数

 【注意】

(1)对于方程的解,变量类型应该设置为double型

(2)代码段的最后两行提供了两种输出方式,一种是C语言版本,一种是C++版本;

(3)注意中点mid的求法,这里的区间不是数组下标,可以直接用(R + L) / 2

#include<iostream>
#include<iomanip>
using namespace std;
double f(double x)
{
	return x * x * x + 16;
}
int main()
{
	double L = -1e9, R = 1e9;
	while (R - L >= 1e-6)
	{
		double mid = (R + L) / 2;	//注意这里中点的求法
		if (f(mid) > 0)
			R = mid;
		else
			L = mid;
	}
	printf("%.5lf\n", L);
	cout << fixed<<setprecision(5)<<L << endl;
}

 【精度问题的讨论】如果我们将精度提高到小数点后15位呢?

  尝试改写上述代码:

	while (R - L >= 1e-16)
	
	printf("%.15lf\n", L);
	cout << fixed<<setprecision(15)<<L << endl;

 得到的运行结果如下:

         我们可以看到,并没有运行结果,说明程序陷入了死循环,那么原因是什么呢?为什么精度要求变高之后,会得不到输出结果呢?

        这是因为double本身存在不小的精度误差,当我们通过R - L\geqslant10^{-16}这样的方式来控制二分的终止条件,会出现很大的精度问题。

【解决办法】

        可以采用固定次数二分的方法:

for (int i = 1; i <= 100; i++)
	{
		double mid = (R + L) / 2;	//注意这里中点的求法
		if (f(mid) > 0)
			R = mid;
		else
			L = mid;
	}

 【注意】2^{100}大约是10^{30},二分的初始条件是10^{9},最后足以把输出结果控制在10^{-20}左右。

得到结果如下:

【查找x第一次出现的位置】

        可以利用lower_bound ()这个函数,因为该函数返回的是一个地址,那么用得到的地址减去头指针即为 x 的位置

int pos = lower_bound(a + 1, a + n + 1, x) - a;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值