ACM-二分查找

在有序表中高效查找元素的常用方法是二分查找,所谓二分即是折半,遵循分治的思想,每次将元序列划分成数量尽量相等的两个子序列,然后递归查找,最终定位到目标元素。下面是二分查找实现代码(假设data序列按递增序排列):

int binary_search(int *data, int size, int value)
{
    int mid;
    int left = 0;
    int right = size - 1;
    while(left < right)
    {
        // 确保中点靠近区间的起点
        mid = left + (right-left)/2;
        // 如果找到则返回
        if(data[mid] == value) return mid;
        // 将中点赋给终点
        else if(data[mid] > value) right = mid;
        // 将中点加一赋给起点
        else left = mid + 1;
    }
    return -1;
}

上述代码存在一个问题,就是当序列中存在多个value的时候,bsearch会返回最中间的那一个元素,有时候这不是我们想要的结果,换句话说这样就无法确定值等于value的完整区间,所以还需要对上面的代码进行改进,使得其可以查找出value出现的第一个位置和最后一个位置,其实就是对等于情况的讨论,下面的代码实现了这个功能:

/*
该函数当value存在时返回它出现的第一个位置,
如果value不存在则返回位置i,满足在该位置插入value后该序列仍然有序
*/
int lower_bound(int *data, int size, int value)
{
    int mid;
    int left = 0;
    int right = size - 1;
    while(left < right)
    {
        // 确保中点靠近区间的起点
        mid = left + (right-left)/2;
        // 将中点赋给终点,包含等于情况
        if(data[mid] >= value) right = mid;
        // 将中点加一赋给起点
        else left = mid + 1;
    }
    return left;
}

/*
该函数当value存在时返回它出现的最后一个位置的后面一个位置,因为起点会移动到中点加一
如果value不存在则返回位置i,满足在该位置插入value后该序列仍然有序
*/
int upper_bound(int *data, int size, int value)
{
    int mid;
    int left = 0;
    int right = size - 1;
    while(left < right)
    {
        // 确保中点靠近区间的起点
        mid = left + (right-left)/2;
        // 将中点赋给终点
        if(data[mid] > value) right = mid;
        // 将中点加一赋给起点,包含等于情况
        else left = mid + 1;
    }
    return left;
}

所以,实现了前面两个函数,就可以使用它们来找出value的出现范围,假设lower_bound和upper_bound的返回值分别为L和R,那么value出现的子序列为[L,R),注意是前闭后开的一个区间,其实当value不存在时也成立,此时L等于R,即区间为空。

上面的讨论涉及到了二分查找的各种情况下的原理实现,但其实STL里面也已经实现了binary_search、lower_bound和upper_bound函数,功能即和前面讨论的差不多,使用方法也类似于前面实现的函数,使用时只需要包含algorithm头文件即可。

下面以一道题为例,应用二分查找算法,HDOJ(2141),时空转移(点击打开链接),题目如下:

Can you find it?

Time Limit: 10000/3000 MS (Java/Others)    Memory Limit: 32768/10000 K (Java/Others)
Total Submission(s): 13949    Accepted Submission(s): 3581


Problem Description
Give you three sequences of numbers A, B, C, then we give you a number X. Now you need to calculate if you can find the three numbers Ai, Bj, Ck, which satisfy the formula Ai+Bj+Ck = X.
 

Input
There are many cases. Every data case is described as followed: In the first line there are three integers L, N, M, in the second line there are L integers represent the sequence A, in the third line there are N integers represent the sequences B, in the forth line there are M integers represent the sequence C. In the fifth line there is an integer S represents there are S integers X to be calculated. 1<=L, N, M<=500, 1<=S<=1000. all the integers are 32-integers.
 

Output
For each case, firstly you have to print the case number as the form "Case d:", then for the S queries, you calculate if the formula can be satisfied or not. If satisfied, you print "YES", otherwise print "NO".
 

Sample Input
   
   
3 3 3 1 2 3 1 2 3 1 2 3 3 1 4 10
 

Sample Output
   
   
Case 1: NO YES NO
 

题意:

给出三个数列A,B,C,再给出一个数X,问能否分别在数列A、B、C中找出一个数,满足它们的和等于X,即Ai+Bj+Ck = X。

分析:

如果暴力枚举,复杂度是O(n^3),会超时;如果将公式变一下型,Ai+Bj = X-Ck,就只需要枚举Ai+Bj,复杂度变为O(n^2),所以只需要先将Ai+Bj的和保存在sum中,然后再对X-Ck在sum中进行二分查找即可。

源代码:

#include <cstdio>
#include <algorithm>
#include <cstring>

#define LL __int64
using namespace std;

int flag[5];
LL data[5][505];
LL sum[505*505];
char ans[1005][5];

int binary_search(LL *sum, int size, LL value)
{
    int mid;
    int left = 0;
    int right = size - 1;
    while(left < right)
    {
        mid = left + (right-left)/2;
        if(sum[mid] == value) return mid;
        else if(sum[mid] > value) right = mid;
        else left = mid + 1;
    }
    return -1;
}

int main()
{//freopen("sample.txt", "r", stdin);
    int cas=1, l, n, m;
    while(~scanf("%d%d%d", &l, &n, &m))
    {
        memset(data, 0, sizeof(data));

        flag[0] = l;
        flag[1] = n;
        flag[2] = m;
<span style="white-space:pre">	</span>// 录入数列A、B、C
        for(int i=0; i<3; ++i)
        {
            for(int j=0; j<flag[i]; ++j)
            {
                scanf("%I64d", &data[i][j]);
            }
            sort(data[i], data[i]+flag[i]);
        }
<span style="white-space:pre">	</span>// 计算A+B的和
        int cnt = 0;
        for(int i=0; i<l; ++i)
        {
            for(int j=0; j<n; ++j)
            sum[cnt++] = data[0][i] + data[1][j];
        }
        sort(sum, sum+cnt);

        int num, x;
        scanf("%d", &num);
        for(int i=0; i<num; ++i)
        {
            scanf("%d", &x);
            int j = 0;
            for(; j<m; ++j)
            if(binary_search(sum, cnt, x-data[2][j]) != -1)
            {
                strcpy(ans[i], "YES");
                break;
            }
            if(j == m) strcpy(ans[i], "NO");
        }
        printf("Case %d:\n", cas++);
        for(int i=0; i<num; ++i) puts(ans[i]);
    }
    return 0;
}

其它类似的题目还有,HDOJ:1597、4004。

下一篇文章讨论与二分比较类似的一个搜索算法,即三分搜索,传送门(点击打开链接)。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值