zoukankan      html  css  js  c++  java
  • 二分搜索——不光是查找值

    摘要:

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

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

       基本思想

      二分搜索法的基本思想是通过不断的缩小解可能存在的范围,从而求得问题最优解的方法。比如一个直观的问题是在一个由小到大的数列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
    ", 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
    ", 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
    ", 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
    ", 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 }

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

  • 相关阅读:
    什么样的代码称得上是好代码?
    九年程序人生 总结分享
    Docker入门 第一课 --.Net Core 使用Docker全程记录
    阿里云 Windows Server 2012 r2 部署asp.net mvc网站 平坑之旅
    Visual studio 2015 Community 安装过程中遇到问题的终极解决
    Activiti6.0 spring5 工作流引擎 java SSM流程审批 项目框架
    java 进销存 库存管理 销售报表 商户管理 springmvc SSM crm 项目
    Leetcode名企之路
    24. 两两交换链表中的节点
    21. 合并两个有序链表
  • 原文地址:https://www.cnblogs.com/wenzhixin/p/9630277.html
Copyright © 2011-2022 走看看