C/C++:二分法查找

二分法查找

#include <iostream>
#include <cstdio>
using namespace std;

// a是有序数组(从小到大排序),length是数组长度,num是指定元素
int BinarySearch(int a[], int length, int num)
{
    // 初始查找区间:[0, length-1]
    int L = 0;  // 查找区间的左端点
    int R = length - 1;  // 查找区间的右端点

    while(L <= R) {  // 如果查找区间不为空,就继续查找
        int mid = L + (R - L) / 2;  // 查找区间的中间值
        // int mid = (L + R) / 2;  // L+R的值可能过大导致溢出,因此不使用该语句
        // cout << "L = " << L << ", R = " << R << ", mid = " << mid << endl;

        if(num > a[mid]) {  // 如果num在a[mid]的右边
            // 设置新的查找区间:[mid+1, R]
            L = mid + 1;
        }
        else if(num < a[mid]) {  // 如果p在a[mid]的左边
            // 设置新的查找区间:[L, mid-1]
            R = mid - 1;
        }
        else {  // 如果正好找到,返回下标
            return mid;
        }
    }
    return -1;  // 如果没有找到,返回-1
}

int main()
{
    // 在有序数组a中查找指定元素num的下标
    int a[10] = {2, 6, 11, 23, 32, 44, 51, 73, 90, 108};  // 有序数组
    int length = sizeof(a) / sizeof(int);  // 数组长度
    int num = 23;  // 指定元素

    int r = BinarySearch(a, length, num);
    if(r < 0)  // 如果没有找到
        cout << "No Found." << endl;
    else
        cout << "Index of " << a[r] << " is " << r << endl;
    
    return 0;
}

例题:找一对数

问题:输入 n(n≤100,000) 个整数,找出其中的两个数,它们的和等于整数 m(假定肯定有解)。

解法1:用两重循环,枚举所有的取数方法,复杂度是 O(n2)。

#include <iostream>
#include <cstdio>
using namespace std;

int main()
{
    int n, m;
    cout << "Input n: "; cin >> n;  // 数组长度
    cout << "Input m: "; cin >> m;  // 两个数的和
    int a[n];
    cout << "Input array: ";
    for(int i = 0; i < n; i++) {  // 输入n个整数作为无序数组的元素
        cin >> a[i];
    }

    for(int i = 0; i < n; i++) {  // 循环遍历第一个数
        for(int j = i + 1; j < n; j++) {  // 循环遍历第二个数
            if(a[i] + a[j] == m) {  // 如果这两个数正好符合条件
                cout << a[i] << ", " << a[j] << endl;
                break;
            }
        }
    }

    return 0;
}

解法2:将数组排序,复杂度是 O(n×log(n))。对数组中的每个元素 a[i],用二分法查找 m-a[i],复杂度是 O(n×log(n))。总的复杂度是 O(n×log(n))。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

// a是有序数组(从小到大排序),length是数组长度,num是指定元素
int BinarySearch(int a[], int length, int num)
{
    int L = 0;  // 查找区间的左端点
    int R = length - 1;  // 查找区间的右端点
    while(L <= R) {  // 如果查找区间不为空,就继续查找
        int mid = L + (R - L) / 2;  // 查找区间的中间值
        if(num > a[mid])  // 如果num在a[mid]的右边
            L = mid + 1;
        else if(num < a[mid])  // 如果p在a[mid]的左边
            R = mid - 1;
        else  // 如果正好找到,返回下标
            return mid;
    }
    return -1;  // 如果没有找到,返回-1
}

int main()
{
    int n, m;
    cout << "Input n: "; cin >> n;  // 数组长度
    cout << "Input m: "; cin >> m;  // 两个数的和
    int a[n];
    cout << "Input array: ";
    for(int i = 0; i < n; i++) {  // 输入n个整数作为无序数组的元素
        cin >> a[i];
    }

    sort(a, a+n);  // 将数组排序
    for(int i = 0; i < n; i++) {  // 循环遍历第一个数
        int j = BinarySearch(a, n, m-a[i]);  // 用二分法查找第二个数
        if(j >= 0) {  // 如果查找结果不为-1,即找到第二个数
            cout << a[i] << ", " << a[j] << endl;
            break;
        }
    }

    return 0;
}

解法3:将数组排序,复杂度是 O(n×log(n))。设置两个变量 i 和 j 进行查找,i 的初值是0,j 的初值是 n-1,计算 a[i]+a[j],如果大于 m 则 j-1,如果小于 m 则 i+1,直到等于 m,复杂度是 O(n)。总的复杂度是 O(n×log(n))。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

// a是有序数组(从小到大排序),length是数组长度,num是指定元素
int BinarySearch(int a[], int length, int num)
{
    int L = 0;  // 查找区间的左端点
    int R = length - 1;  // 查找区间的右端点
    while(L <= R) {  // 如果查找区间不为空,就继续查找
        int mid = L + (R - L) / 2;  // 查找区间的中间值
        if(num > a[mid])  // 如果num在a[mid]的右边
            L = mid + 1;
        else if(num < a[mid])  // 如果p在a[mid]的左边
            R = mid - 1;
        else  // 如果正好找到,返回下标
            return mid;
    }
    return -1;  // 如果没有找到,返回-1
}

int main()
{
    int n, m;
    cout << "Input n: "; cin >> n;  // 数组长度
    cout << "Input m: "; cin >> m;  // 两个数的和
    int a[n];
    cout << "Input array: ";
    for(int i = 0; i < n; i++) {  // 输入n个整数作为无序数组的元素
        cin >> a[i];
    }

    sort(a, a+n);  // 将数组排序
    int i = 0, j = n - 1;  // 用i从前往后查找,用j从后往前查找
    while(i < j) {
        if(a[i] + a[j] > m)  // 如果结果大于m
            j--;  // j应该往前移使结果减小
        else if(a[i] + a[j] < m)  // 如果结果小于m
            i++;  // i应该往后移使结果增大
        else {  // a[i] + a[j] == m
            cout << a[i] << ", " << a[j] << endl;
            break;
        }
    }

    return 0;
}

输入:
n = 10,m = 50,a[n] = {11, 54, 23, 32, 44, 6, 10, 13, 2, 26}

输出:
在这里插入图片描述


例题:农夫和奶牛

问题:农夫建造了一座很长的畜栏,有 N(2≤N≤100,000) 个隔间,这些隔间的位置是 x0, x1, …, xN-1(0≤xi≤1,000,000,000),xi 均为整数,各不相同。有 C(2≤C≤N) 头牛,每头牛分到一个隔间,如何使任意两头牛之间的最小距离尽可能大?这个最大的最小距离是多少?

解法1:将数组排序,得到排序后的隔间坐标 x[N]。从 1,000,000,000/C 到 1 依次尝试最大的最小距离 D,直到找到第一个可行的值,复杂度是 1,000,000,000/C×N。
尝试方法:第 1 头牛放在 x0,……若第 k 头牛放在 xi,则找到 xi+1 到 xN-1 中第一个位于 [Xi+D, 1,000,000,000] 的 xj,第 k+1 头牛放在 xj,如果找不到这样的 xj 则 D-1,继续尝试。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

#define MaxXi 100  // xi的最大值

// 判断D是否可行
bool JudgeDis(int D, int N, int C, int x[], int cow[])
{
    cow[0] = 0;  // 第0号牛放在第0号隔间,隔间坐标是x[0]
    int i, j, k;
    for(k = 1; k < C; k++) {  // 对其余每头牛(第1~C-1号)分配隔间
        i = cow[k-1];  // 如果第k-1号牛放在第i号隔间,隔间坐标是x[i]
        for(j = i + 1; j < N; j++) {  // 从第i号隔间向后查找
            if(x[j] >= x[i] + D) {  // 如果找到符合条件的x[j]
                cow[k] = j;  // 第k号牛放在第j号隔间,隔间坐标是x[j]
                break;
            }
        }
        if(j == N)  // 如果找不到x[j],即D不可行
            return false;
    }
    if(k == C)  // 如果所有牛都能放在隔间中,即D可行
        return true;
}

int main()
{
    int N, C;
    cout << "Input N: "; cin >> N;  // 隔间的数量
    cout << "Input C: "; cin >> C;  // 牛的数量
    int x[N];
    cout << "Input array: ";
    for(int i = 0; i < N; i++) {  // 输入N个隔间的位置或坐标
        cin >> x[i];
    }
    int cow[C];  // 牛的隔间序号

    sort(a, a+n);  // 将数组排序
    for(int D = MaxXi / C; D > 0; D--) {  // 尝试最大的最近距离
        if(JudgeDis(D, N, C, x, cow)) {  // 如果D可行
            cout << "Maximum nearest distance: " << D << endl;
            break;
        }
    }
    for(int i = 0; i < C; i++) {
        cout << "Cow number: " << i + 1 << ", Cow location: " << x[cow[i]] << endl;
    }
    
    return 0;
}

解法2:将数组排序,得到排序后的隔间坐标 x[N]。在 [L, R] 内用二分法尝试最大的最小距离 D=(L+R)/2,如果 D 可行则记录该 D 并且 L=D+1,如果 D 不可行则 R=D-1,复杂度是 log(1,000,000,000/C)×N。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

#define MaxXi 100  // xi的最大值

// 判断D是否可行
bool JudgeDis(int D, int N, int C, int x[], int cow[])
{
    cow[0] = 0;  // 第0号牛放在第0号隔间,隔间坐标是x[0]
    int i, j, k;
    for(k = 1; k < C; k++) {  // 对其余每头牛(第1~C-1号)分配隔间
        i = cow[k-1];  // 如果第k-1号牛放在第i号隔间,隔间坐标是x[i]
        for(j = i + 1; j < N; j++) {  // 从第i号隔间向后查找
            if(x[j] >= x[i] + D) {  // 如果找到符合条件的x[j]
                cow[k] = j;  // 第k号牛放在第j号隔间,隔间坐标是x[j]
                break;
            }
        }
        if(j == N)  // 如果找不到x[j],即D不可行
            return false;
    }
    if(k == C)  // 如果所有牛都能放在隔间中,即D可行
        return true;
}

int main()
{
    int N, C;
    cout << "Input N: "; cin >> N;  // 隔间的数量
    cout << "Input C: "; cin >> C;  // 牛的数量
    int x[N];
    cout << "Input array: ";
    for(int i = 0; i < N; i++) {  // 输入N个隔间的位置或坐标
        cin >> x[i];
    }
    int cow[C];  // 牛的隔间序号

    sort(a, a+n);  // 将数组排序
    int maxD;  // 最大的最近距离
    int cowD[C];  // 在找到最大的最近距离时的牛的间隔序号
    int L = 1, R = MaxXi / C;  // 二分法的初始区间
    while(L <= R) {  // 如果查找区间不为空,就继续尝试D
        int D = L + (R - L) / 2;
        if(JudgeDis(D, N, C, x, cow)) {  // 如果D可行
            maxD = D;  // 记录该D
            memcpy(cowD, cow, C*sizeof(int));  // 记录该D时的牛的间隔序号
            L = D + 1;
        }
        else {  // 如果D不可行
            R = D - 1;
        }
    }

    cout << "Maximum nearest distance: " << maxD << endl;
    for(int i = 0; i < C; i++) {
        cout << "Cow number: " << i + 1 << ", Cow location: " << x[cowD[i]] << endl;
    }

    return 0;
}

输入:
N = 10,C = 4,x[N] = {8, 2, 13, 87, 46, 39, 27, 31, 17, 5}

输出:
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值