1. 程式人生 > >ACM-二分查詢

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。

下一篇文章討論與二分比較類似的一個搜尋演算法,即三分搜尋,傳送門(點選開啟連結)。