Loading

算法笔记--二分

二分查找

对于递增序列,顺序查找的时间复杂度为O(n),如果序列太大,就很难承受

在有序序列中,利用二分查找可以有效减少时间复杂度,其时间复杂度为O(logn)

对于递增数列,二分查找如下:

#include<cstdio>
int binarySearch(int myArray[], int left, int right, int x){
    while(left <= right){
        int mid = (left + right) / 2;
        if(myArray[mid] == x)return mid;
        else if(myArray[mid] < x)
        {
            left = mid + 1;
        }
        else
            right = mid - 1;
    }
    return -1;
}

int main(){
    int myArray[] = {1, 2, 3, 7, 10, 12};
    int x = 3;
    int site = binarySearch(myArray, 0, 5, x);
    printf("the position of %d is %d ", x, site);
}

注意:当二分上界超过int型数据范围的一半,那么当欲查询元素在序列比较靠前的位置时,(left + right)可能会超过int而导致溢出,此时,应该使用mid = left + (right - left) / 2来代替避免溢出

对于重复元素的递增数列,求出序列中第一个大于等于x的元素的位置L以及第一个大于x的元素的位置R,这样元素X在序列中的存在区间就是左闭右开区间 [L, R)

如:对于 {1, 3, 3, 3, 6}

查询3,L = 1, R = 4; 查询5,L = R = 4; 查询6,L = 4, R = 5; 查询8,L = R = 5

int solve(int letf, int right){
    int mid;
    while(left < right){ 		// letf == right 意味着找到唯一位置
        mid = (left + right) / 2;
        if(条件成立){
            right = mid;
        }esle{
            left = mid + 1;
        }
    }
    return letf;
}

对于条件成立

  • 当寻找大于等于第一个数的时,为mid >= x
  • 当寻找第一个大于的数时,为mid > x
  • 注意推导即可

二分法拓展

计算\(√2\)的近似值

#include<cstdio>
double f(double x){
    return x * x;
}

int main(){
    const double eps = 1e-5;
    double left = 1;
    double right = 2;
    double mid;
    while(right - left > eps){
        mid = left + (right - left) / 2;
        if(f(mid) > 2)
            right = mid;
        else
            left = mid;
    }
    printf("%.5f", mid);
    return 0;
}

该例使用与求f(x)的根,逼近√2相当于求\(f(x) = x^2 - 2 = 0\)的零点

装水问题

求 $S_1 / S_2 的为 r $, \(S_1为水的面积\), \(S_2为半圆的面积\)

Snipaste_2020-02-26_15-31-05

#include<cstdio>
#include<cmath>
const double PI = acos(-1.0);
double f(int h, int R){
    double alpha = 2 * acos((R - h) / R);
    double L = 2 * sqrt(R * R - (R - h) * (R - h));
    double S1 = 1/2 * R * R * alpha - 1/2 * L * (R - h);
    double S2 = PI * R * R / 2;
    return S1 / S2;
}

int main(){
    double R, r;
    double mid;
    scanf("%lf %lf", &R, &r);
    double left = 0;
    double right = R;
    while(right - left > 1e-5){
        mid = left + (right - left) / 2;
        if(f(mid, R) > r){
            right = mid;
        }else{
            left = mid;
        }
    }
    printf("当 r = %.2f 时,注水高度为:%.2f", r, mid);
    return 0;
}

快速幂

学习快速幂首先要明白模运算规则

基本运算:
(a + b) % p = (a % p + b % p) % p
(a - b) % p = (a % p - b % p) % p
(a * b) % p = (a % p * b % p) % p
a^b % p = ((a % p)^b) % p
结合律:
((a + b) % p + c) % p = (a + (b + c) % p) % p
((a * b) % p * c) % p = (a * (b * c) % p) % p

给的三个正整数\(a、b、m\), (\(a < 10^9, b < 10^6, 1 < m < 10^9\), 求\(a^b % m\))

如果使用for循环,其时间复杂度为O(b)

#include<cstdio>
typedef long long LL;
LL pow(LL a, LL b, LL m){
    LL ans = 1;
    for(int i = 0; i < b; i++){
        ans = ans * a % m;		// 见下面图推导
    }
    return ans;
}

int main(){
    LL x = pow(3, 10, 5);
    printf("%ld",x);
    return 0;
}
88EBD90BF9F388A7B77F6DA275505733

使用for循环在指数很大的情况下是不行的,所以已经使用快速幂的做法,也称为二分幂

  • 如果b是奇数,那么有\(a^b = a * a^{b - 1}\)
  • 如果b是偶数,那么有\(a^b = a^{b/2} * a^{b/2}\)

快速幂的递归写法:时间复杂度为O(logb)

typedef long long LL;
LL binaryPow(LL a, LL b, LL m){
    if(b == 0) return 1;
    else if(b & 1) return a * binaryPow(a, b - 1, m);
    else{
        LL mul = binaryPow(a, b / 2, m);
        return mul * mul % m;
    }
}

if(b & 1)用于判断b的末位是否为1,等价于b % 2 == 1

快速幂的迭代写法

如13的二进制为1101,等价于\(a^13 = a^{8+4+1} = a^8×a^4×a^1\)

typedef long long LL;
LL binaryPow(LL a, LL b, LL m){
    LL ans = 1;
    while(b > 1){
        if(b & 1){				// 如果b的二进制末尾为1
            ans = ans * a % m;	// 令ans累积上a
        }
        a = a * a % m;
        b >> 1;					// 将b的二进制右移1位,即 b = b >> 1或 b = b / 2 
    }
    return ans;
}
posted @ 2020-03-09 21:55  ZHGQCN  阅读(239)  评论(0编辑  收藏  举报