二分搜索——不光是查找值

摘要:

  本文主要讲述了二分搜索算法的基本思想和实现原理,着重讲解了二分搜索法在编程竞赛中的一些典型应用。

  • 基本思想
  • 实现原理
  • 典型应用
  • 例题解析

 

   基本思想

  二分搜索法的基本思想是通过不断的缩小解可能存在的范围,从而求得问题最优解的方法。比如一个直观的问题是在一个由小到大的数列a中找到一个数ai,使得ai满足ai>=k的同时,i最小。由于此数列是有序的,所以找到中间位置的数amid和k比较,如果k<=amid,证明k在左半区域,区间左边界变为mid,否则在右半区域,区间右边界变为mid。如此下来,直至区间长度小于1,结束循环,最小的i就是右边界的值。

  二分搜索的优势在于时间上的高效,每次去掉一半的搜索区间,可以在O(logn)的时间复杂度求得最终的解。但是这种高效是建立在事先将数排好序的基础上的。

   实现原理

  反复与区间的中点进行比较,不断的将解的范围缩小到原来的一半,直至满足一定的条件后结束算法。简单起见,直接看一道VJudge上的例题Lower Bound ,具体细节参考如下代码:

 1 #include <cstdio>
 2 
 3 const int maxn = 100000 + 10;
 4 int a[maxn];
 5 int n, q;
 6 
 7 void query(int k) {
 8     int lb = -1, ub = n;//初始化区间短点 
 9     while(ub - lb > 1) {//结束条件是当区间的长度小于1的时候 
10         int mid = (ub + lb) / 2;
11         //通过判断缩小区间为原来的一半 
12         if(a[mid] >= k)
13             ub = mid;
14         else
15             lb = mid;
16     }
17     
18     printf("%d\n", ub);
19 }
20 
21 int main()
22 {
23     while(scanf("%d", &n) != EOF) {
24         for(int i = 0; i < n; i++) {
25             scanf("%d", &a[i]);
26         }
27         
28         scanf("%d", &q);
29         while(q--) {
30             int k;
31             scanf("%d", &k);
32             query(k);//查询k 
33         }
34     }
35     return 0;
36 }

  当然C++中的STL以lower_bound函数的形式实现了二分搜索,直接可以调用。参考代码如下:

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <vector>
 4 using namespace std;
 5 
 6 const int maxn = 100000 + 10;
 7 int n, q;
 8 vector<int> v;
 9 
10 int main () {
11     while(scanf("%d", &n) != EOF) {
12         v.clear();
13         for(int i = 0; i < n; i++) {
14             int tmp;
15             scanf("%d", &tmp);
16             v.push_back(tmp);
17         }
18         
19         scanf("%d", &q);
20         while(q--) {
21             int k;
22             scanf("%d", &k);
23             
24             printf("%d\n", lower_bound(v.begin(), v.end(), k) - v.begin());
25         }
26     }    
27     return 0;
28 }

  在编程竞赛中的典型应用

  二分搜索算法除了在有序数组中查找值之外,在求解最优解的问题上也非常有用。比如求满足某个条件C(x)的最小x,我们可以假定一个区间,取该区间的中点值为mid,如果满足C(mid),左边界等于区间中点,即将范围缩小在右侧(最大化),如果不满足,那么右边界等于区间中点,即将范围缩小在左侧。直到将区间缩小到一定的程度后,在此精度下的问题最优解就求出来了。

  1.假定一个解并判断是否可行,例如POJ 1064 Cable master

  2.最大化最小值,例如POJ 2456 Aggressive cows

  3.最大化平均值,例如POJ 3111 K Best

  例题解析

  POJ 1064 Cable master

  有N条绳子,长度分别是Li,如果从它们中切割出K条长度相同的绳子的话,每条绳子最长是多少

    我们将区间设为0到INF(最大),然后尝试区间的中点,向满足条件的最大值逼近(循环100次精度大概是1e-30)。条件C(x):Li/x的总和是否大于K,代码如下:(坑点是最后输出保留两位小数,不进位)

 1 #include <cstdio>
 2 #include <cmath>
 3 const int maxn = 10000 + 10;
 4 const int inf = 99999999;
 5 
 6 int N, K;
 7 double L[maxn];
 8 
 9 bool C(double x) {
10     int num = 0; 
11     for(int i = 0; i < N; i++) {
12         num += (int)(L[i] / x);
13     }
14     return num >= K;
15 }
16 void solve() {
17     double lb = 0, ub = inf;
18     for(int i = 0;i < 100; i++) {
19         double mid = (ub + lb) / 2;
20         if(C(mid))
21             lb = mid;
22         else 
23             ub = mid;
24     }
25     
26     printf("%.2f\n", floor(ub * 100) / 100);
27 }
28 int main()
29 {
30     while(scanf("%d%d", &N, &K) != EOF) {
31         for(int i = 0; i < N; i++) {
32             scanf("%lf", &L[i]);
33         }
34         
35         solve();
36     }    
37     return 0;
38 } 

  POJ 2456 Aggressive cows

  将M头牛放在N个牛舍里,使得这M头牛之间的举例尽可能的大。

  意即求解满足C(d)的最大d。而其中的d表示M头牛之间的举例均不小于d。区间的结束条件就是区间长度大于等于1,关键是如何判断满足条件,其实也好判,采用贪心的方法,一次往后使用即可,如果M头牛安排完了,满足,否则不满足。代码如下:

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int maxn = 100000 + 10;
 6 const int inf = 1000000000 + 10;
 7 int N, M;
 8 int x[maxn];
 9 
10 bool C(int d) {
11     int last = 0; 
12     for(int i = 1; i < M; i++) {
13         int crt = last + 1;
14         while(crt < N && x[crt] - x[last] < d) {
15             crt++;
16         }
17         
18         if(crt == N)    return false;
19         last = crt;
20     }
21     return true;
22 }
23 void solve() {
24     sort(x, x + N);
25     
26     int lb = 0, ub = inf;
27     while(ub - lb > 1) {
28         int mid = (ub + lb) / 2;
29         if(C(mid))
30             lb = mid;
31         else
32             ub = mid;
33     }     
34     
35     printf("%d\n", lb);
36 }
37 int main()
38 {
39     while(scanf("%d%d", &N, &M) != EOF) {
40         for(int i = 0; i < N; i++) {
41             scanf("%d", &x[i]);
42         }
43         
44         solve();
45     }
46     return 0;
47 } 

  POJ 3111 K Best

  有n个物品的价值和重量分别是vi和mi,从中选出k件物品使得单位重量的价值最大。

  C(x)=((vi - x * wi) 从大到小排列中的前k个的和不小于0)

  精度1e-30

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int maxn = 100000 + 10;
 6 const double inf = 0x3f3f3f3f;
 7 const double EPS = 1.0e-6;
 8 
 9 int N, K;
10 int v[maxn], w[maxn];
11 struct Node {
12     double val;
13     int id;
14 } y[maxn];
15 
16 bool cmp(struct Node a, struct Node b) {
17     return a.val > b.val;
18 }
19 bool C(double x) {
20     for(int i = 0; i < N; i++) {
21         y[i].id = i + 1;
22         y[i].val = (v[i] - x * w[i]);
23     }
24     
25     sort(y, y + N, cmp);
26     double sum = 0;
27     for(int i = 0; i < K; i++) {
28         sum += y[i].val;
29     }
30     return sum >= 0;
31 }
32 void solve() {
33     double lb = 0, ub = inf;
34     
35     while(ub - lb > EPS) {
36         double mid = (ub + lb) / 2;
37         if(C(mid))
38             lb = mid;
39         else
40             ub = mid;
41     }    
42     
43     for(int i = 0; i < K - 1; i++) {
44         printf("%d ", y[i].id);
45     }
46     printf("%d ", y[K - 1].id);
47 }
48 
49 int main()
50 {
51     while(scanf("%d%d", &N, &K) != EOF) {
52         for(int i = 0; i < N; i++) {
53             scanf("%d%d", &v[i], &w[i]);
54         }
55         
56         solve();
57     }    
58     return 0;
59 }

  至此,二分搜索算法就讲完了,二分的思想就是一次去一半,寻找满足条件的最优,在解决最优解问题的时候要找好结束条件和满足的条件。算法不难,关键是思考问题和实现算法的过程。

 

转载于:https://www.cnblogs.com/wenzhixin/p/9630277.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值