您的位置:首页 > 其它

转:二分查找的3种不同情况详细介绍

2012-11-04 20:52 225 查看

POJ3273 Monthly Expense 二分查找的魅力

转自:http://hi.baidu.com/poeticxuan/item/7ac0d2f292aa930bd6ff8c5d

题意:
给定连续的n天(1<=n<=100000),每天都要花费一定的资金,需要将这连续的n天划分为m段(1<=m<=n),每一段都是连续的,且使得m段中各个段的资金总和的最大值最小,求出这个最小值。

这题让我联想到了曾经笔试中遇到的一道题,题意如下:

【说是有n个站点,每个站点有A[i]个网页(0<=i<=n-1)。有m个网络爬虫,每个爬虫只能对连续的站点进行信息提取,m个爬虫对一个网页的信息提取消耗的时间是相等的,一个时间单位。问设计一个算法,使得m个爬虫对所有站点信息提取的最少时间,并分析时间复杂度。】
这道笔试题和POJ3273本质上是一样的,POJ上类似这样的题还有POJ3258和POJ3122,有空的时候都可以做一下。
思路:
没想到这题可以用二分查找的思路来求解,一直在考虑用动态规划的思想。
当整个n天划分为1个段时,最大值就是n天所需资金的累加总和,我们记做sum,当它划分为n段时,最大值就是n天所需资金中的最大值,我们记做max。我们可以完全肯定的是,我们需要的答案一定在这个范围之内[max,sum],而且我们需要求的是最小值。这个问题其实就变成了在一个有序的区间[max,sum]内求满足条件的最小值,这个条件就是n天可以恰好划分为m段。这样,就可以采用二分查找的思路来求解了,只不过每次需要判定当前的值是否能够让n天恰好划分为m段。
当然,还有一点需要注意,传统的二分查找(我是指最基本的方式)并不能解决该问题,主要是边界条件的判定。这道题其实类似于在有多个相同元素同时存在的有序序列中查找下标最小的满足条件的解。
比如在1,2,2,3,3,3,4,4,4,4这一序列中查找下标最小的3,答案为3,如果用最一般的二分查找就不一定能保证答案是3了,可能是4或者5.
从这题我们可以看出二分查找的魅力,而且要非常灵活地运用二分查找的思想来求解问题并不是很容易的事情。难怪会出现这样的情况:《编程珠玑》(第二版)一书第四章中提及过100多名专业程序员使用两个小时的充足时间编写一个简单的二分查找程序,结果发现90%的人编出的代码都有BUG;Knuth也在他的TAOCP中的《Sorting and Searching》部分提过,第一个二分查找程序在1946年已经公布,但是到了1962年才出现第一个没有BUG的二分查找程序,期间经历了16年的时间。可见,对二分查找的确不可小觑啊。作为一名真正优秀顶尖的技术开发人员,高效地完成bugless的程序才是王道啊。

#include<iostream>
using namespace std;
const int N=100000;
int spends[N+1];
int n,m;
int work()
{
int i,low,high,mid,sum,cnts;
low=high=0;
for(i=0;i<n;i++)
{
high+=spends[i];
low=max(low,spends[i]);
}
if(m==1)return high;
else if(m==n) return low;
while(low<high)
{
mid=(low+high)/2;
sum=cnts=0;
for(i=0;i<n;i++)
{
sum+=spends[i];
if(sum>mid)
{
sum=spends[i];
cnts++;
}
}
cnts++;
if(cnts<=m) high=mid;
else low=mid+1;
}
return high;
}
int main()
{
int i;
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
scanf("%d",&spends[i]);
printf("%d\n",work());
return 0;
}

下面再附上三种版本的二分查找,分别是基本型,求相同值中下标最小的二分查找以及求相同值中下标最大的二分查找,注意这三者之间的微妙区别,如果能够彻底明白这些微妙的边界条件,那我想对于二分查找的精髓就算融会贯通了。

#include<iostream>
using namespace std;
//基本型的二分查找
int bsearch(int a[],int l,int r,int key)
{
int m;
while(l<=r)//while循环中需要两次比较
{
m=l+(r-l)/2;
if(a[m]==key)
return m;
else if(a[m]>key)
r=m-1;
else
l=m+1;
}
return -1;
}
//求相同值存在时下标值最小的二分查找
int bsearch1(int a[],int l,int r,int key)
{
int m;
while(l<r)//注意区别,while循环中只有一次比较
{
m=l+(r-l)/2;
if(a[m]>=key)
r=m;
else
l=m+1;
}
if(a[l]!=key)
return -1;
return l;
}
//求相同值存在时下标值最大的二分查找
int bsearch2(int a[],int l,int r,int key)
{
int m;
while(l<r)//注意区别,while循环中只有一次比较
{
m=l+(r-l)/2+1;//注意与bsearch1的区别,这是为了能够顺利退出while循环
if(a[m]<=key)
l=m;
else
r=m-1;
}
if(a[l]!=key)
return -1;
return l;
}
int main()
{
//简单的测试案例
int i,a[15]={1,2,2,3,3,3,4,4,4,4,5,5,5,5,5};
for(i=0;i<=6;i++)
{
printf("%d:%d\n",i,bsearch(a,0,14,i));
printf("%d:%d\n",i,bsearch1(a,0,14,i));
printf("%d:%d\n",i,bsearch2(a,0,14,i));
}
return 0;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: