前言
笔者虽然刷的算法题不多,但是笔者也敢说,二分法真的是一种很优越的算法,使用上限极高的那种,正因如此,笔者才想浅谈一下二分法.
封面是我很喜欢的一个游戏角色,不知道有没有老gal玩家知道!
什么是二分法?
枚举查找即顺序查找,实现原理是逐个比较数组 a[0:n-1]
中的元素,直到找到元素 x
或搜索整个数组后确定 x
不在其中。最坏情况下需要比较 N
次,时间复杂度是 O(n)
,属于线性阶算法。
而二分查找是一种折半查找方法。该方法将 N
个元素分成大致相等的两部分,选取中间元素与查找的元素进行比较。如果相等,则查找成功;如果查找元素小于中间元素,则在左半区继续查找;如果查找元素大于中间元素,则在右半区继续查找。每次都将范围缩小至原来的一半,因此时间复杂度是 O(log2n)
。需要注意的是,二分查找的前提是数组有序,一般是从小到大排列。
相信大家伙学c语言的时候就接触过了,笔者上一行代码给大家看看
C++ 版本
// C++ 的版本
while (low < high)
{
int mid = (low + high) / 2;
if (a[mid] >= x)
high = mid;
else
low = mid + 1;
}
while (low < high)
{
int mid = (low + high + 1) / 2;
if (a[mid] <= x)
low = mid;
else
high = mid - 1;
}
java版本
while (low < high) {
int mid = (low + high) / 2;
if (a[mid] >= x)
high= mid;
else
low = mid + 1;
}
while (low < high) {
int mid = (low + high + 1) / 2;
if (a[mid] <= x)
low = mid;
else
high = mid - 1;
}
看得出来,真的没什么区别(哈哈哈!) 毕竟语言有界,算法无界!
时间复杂度
接下来,我们来分享它的时间复杂度
所谓时间复杂度,说白了,就是一个代码算法执行的次数
那么二分的时间复杂度怎么算?
答:O(log2n)
如图所示(有点丑别介意)
我们假设最坏情况,需要查询(执行)y次才能找到我们要的数,那么每次都是二分,2^y 就能代表总数N,所以执行y=log2n 即 时间复杂度为O(log2n)
与n相比O(log2n) 不就是很大的提升了吗?
刚刚分享的是二分整数,还有精度较高的写法,以下代码来自
我也上过这位老师的课,给大家推荐下!!!!
//模版一:实数域二分,设置eps法
//令 eps 为小于题目精度一个数即可。比如题目说保留4位小数,0.0001 这种的。那么 eps 就可以设置为五位小数的任意一个数 0.00001- 0.00009 等等都可以。
//一般为了保证精度我们选取精度/100 的那个小数,即设置 eps= 0.0001/100 =1e-6
while (l + eps < r)
{
double mid = (l + r) / 2;
if (pd(mid))
r = mid;
else
l = mid;
}
//模版二:实数域二分,规定循环次数法
//通过循环一定次数达到精度要求,这个一般 log2N < 精度即可。N 为循环次数,在不超过时间复杂度的情况下,可以选择给 N 乘一个系数使得精度更高。
for (int i = 0; i < 100; i++)
{
double mid = (l + r) / 2;
if (pd(mid))
r = mid;
else
l = mid;
}
部分题目分享
笔者接下来分享写过的题目
题目一 —— 来自蓝桥的计算方程
11.计算方程【算法赛】 - 蓝桥云课 (lanqiao.cn)
给大家伙看看题目
计算机又不会解方程,那我们咋办嘛?
就好比高中的选择压轴题,我们套答案!
怎么套! 一个个列举呗
怎么列举?难道从1开始一个个去列举吗?那要什么时候列举的出来?
那咋办吗? 这个时候就需要二分了,找到适合的答案,且以较低的复杂度
C++
#include <iostream>
#include <cmath>
#define ll long long
int k, m;
int check(int x)
{
if (sqrt(x) + floor(log(x) / log(k)) - m > 0)
return 1;
return 0;
}
void solve()
{
std::cin >> k >> m;
int l = 1, r = 1000000000;
while (l < r)
{
int mid = (l + r) / 2;
if (check(mid))
r = mid;
else
l = mid + 1;
}
std::cout << l << std::endl;
}
int main()
{
int t;
std::cin >> t;
while (t--)
{
solve();
}
return 0;
}
Java
import java.util.Scanner;
public class Main {
static int k, m;
static int check(int x) {
if (Math.sqrt(x) + Math.floor(Math.log(x) / Math.log(k)) - m > 0)
return 1;
return 0;
}
static void solve() {
Scanner scanner = new Scanner(System.in);
k = scanner.nextInt();
m = scanner.nextInt();
int l = 1, r = 1000000000;
while (l < r) {
int mid = (l + r) / 2;
if (check(mid))
r = mid;
else
l = mid + 1;
}
System.out.println(l);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int t = scanner.nextInt();
while (t-- > 0) {
solve();
}
scanner.close();
}
}
套公式走
耗时不太长!!!
题目二——来自洛谷的银行贷款
P1163 银行贷款 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
本人题解如下
#include <bits/stdc++.h>
using namespace std;
double eps = 1e-4; // 精度
double a, b, c;
int main() {
cin >> a >> b >> c;
double l = 0;
double r = 100; // 调整搜索区间上限为100
while (l + eps < r) {
double mid = (l + r) / 2;
double num = a;
// 表示需要还的钱
for (int i = 1; i <= c; i++) {
num = num * (1 + mid / 100) - b;
}
if (num > 0) {
r = mid; // 如果剩余金额大于0,说明利率太高,调低上限
} else {
l = mid; // 否则,调高下限
}
}
printf("%.1lf", l); // 输出结果,保留一位小数
return 0;
}
二分求利率,但是但是,这题笔者并没有AC
具体问题我还要再看看,所以以后会更新的.
题目三——(冶炼金属)
本质上也是二分找答案,不过要分两次, 一次找最大值,一次找最小值
代码如下
#include <iostream>
using namespace std;
int a[10000];
int b[10000];
int n;
int check1(int x) {
for (int i = 1; i <= n; i++) {
if (b[i] < a[i] / x) {
return 1;
}
}
return 0;
}
int check2(int y) {
for (int i = 1; i <= n; i++) {
if (b[i] > a[i] / y) {
return 1;
}
}
return 0;
}
int main() {
// 请在此输入您的代码
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
}
int lmin = 0;
int rmin = 1000000000;
while (lmin + 1 != rmin) {
int mid = (lmin + rmin) / 2;
if (check1(mid)) {
lmin = mid;
} else {
rmin = mid;
}
}
int lmax = 0;
int rmax = 1000000000;
while (lmax + 1 != rmax) {
int mid2 = (lmax + rmax) / 2;
if (check2(mid2)) {
rmax = mid2;
} else {
lmax = mid2;
}
}
cout << rmin << ' ' << rmax - 1;
return 0;
}
找最小值的二分
int check1(int x) {
for (int i = 1; i <= n; i++) {
if (b[i] < a[i] / x) {
return 1;
}
}
return 0;
}
找最大值的二分
int check2(int y) {
for (int i = 1; i <= n; i++) {
if (b[i] > a[i] / y) {
return 1;
}
}
return 0;
}
然后就可以得到答案了
题目四——分巧克力
也是典型的,二分答案, 题解如下
#include <iostream>
# include<bits/stdc++.h>
using namespace std;
const int N=1e6;
int n,k;
int hi[N];
int wi[N];
//1.形状相同
//2.大小相等
//3.最大边长
//4.就是二分
int pd(int x)
{
int sum=0;
for(int j=1;j<=n;j++)
// 通过循环,先看从第一个巧克力开始,到第n个巧克力,算出在x边长下能有多少巧克力给分出来
{
sum=sum+((hi[j]/x)*(wi[j]/x));
}
if(sum>=k)
// 分的数量大于 指定数量,就可以增加边长了
{
return 1;
}
else
// 不然的话,就减小边长
{
return 0;
}
}
int main()
{
// 请在此输入您的代码
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>hi[i]>>wi[i];
}
int r;
for(int i=1;i<=n;i++)
{
r=max(hi[i],r);
r=max(wi[i],r);
}
// 这一步,求得是 r的最大值
int l=0;
int ans=0;
while(l+1!=r)
{
ans=(l+r)/2;
if(pd(ans))
{
l=ans;
}
else
{
r=ans;
}
}
cout<<l<<endl;
return 0;
}
注释已经帮大伙写好了!!!!!
结尾
今天看书,又看到了关于时间复杂度的内容,故而有感而发,写下这篇博客,赏个脸就点赞呗,谢谢谢谢谢!!!!!!!