在有序表中高效查找元素的常用方法是二分查找,所谓二分即是折半,遵循分治的思想,每次将元序列划分成数量尽量相等的两个子序列,然后递归查找,最终定位到目标元素。下面是二分查找实现代码(假设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
3 3 3 1 2 3 1 2 3 1 2 3 3 1 4 10
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。
下一篇文章讨论与二分比较类似的一个搜索算法,即三分搜索,传送门(点击打开链接)。