Day1-二分

【阅读部分】二分查找

在一个有序的数据序列里,如果要查找一个数,普通的办法是从头到尾扫描一遍,但是这样的时间复杂度是O(n)级别。如果有m次查询,那么时间复杂度就达到了O(mn)级别,数据大就超时了。

下面介绍一种O(lg(n))的查找方法,那就是二分查找。

二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。

步骤:首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

二分查找,只要查找log2(n)次,即可找出答案。而log2(1000000)≈20,速度实在是高效得逆天。

折半查找有递归写法和非递归写法。

1、递归写法:

int bin_search(int key[],int low, int high,int k)  //注意:high的值是数组的最后一个单元位置,不是数组元素个数 
//key 源数据数组,假设已从小到大排序 。low:查找区域的前端点;high:查找区域的后端点;k:要查找的值 
{  
    int mid;  
    if(low>high)  //如果数组里没有要查找的数据,返回 -1 
        return -1;  
    else{  
        mid =low+ (high-low) / 2;  //先计算查找区域的中点位置。也有写成:mid = (low+high) / 2;  但如果low和high很大会有溢出int范围的风险 
        if(key[mid]==k)           //如果mid位置的值就是要查找的,就把mid的值返回 
            return mid;  
        if(k>key[mid])            //如果要找的值比mid位置的值大,那么就到后半部分查找 
            return bin_search(key,mid+1,high,k);        /*在序列的后半部分查找*/  
        else  
            return bin_search(key,low,mid-1,k);            /*在序列的前半部分查找*/  
    }  
}

主程序调用:weizhi=bin_search(a,0,n-1,x); //假设a数组已从小到大排序。n是数组数据个数,注意要 -1 。x是要找的数据

2、非递归写法:

int bin_search(int key[],int n,int k)// key 源数据数组,假设已从小到大排序 。n:数组数据个数;k:要查找的值 
    int l=0,r=n-1;  // l:数组起点位置  r:数组终点位置,是数据总个数 -1 
    while(l<=r){
        int mid =l+ (r-l) / 2;  //如果n不大的话也可以写成:mid=(l+r)/2;
        if(key[mid]>k)r=mid-1;  //如果要找的数在前半段,那么修改后端点的值 
        else if(key[mid]<k)l=mid+1;  //如果要找的数在后半段,那么修改前端点的值 
        else return mid;    //如果mid位置的值就是要查找的,就把mid的值返回 
    }
    return -1;  //如果数组里没有要查找的数据,返回 -1 
}

二分法

经常有这样的问题,求xxx最小值的最大值,即求符合条件的值里的最大值(或者求xxx最大值的最小值),这种问题有个解法叫二分答案法。一听,什么,不知道的答案也能二分?嗯没错,关键在于这个答案是可以判断是不是符合条件的。

算法思想

以求最小值的最大值(最小值最大化)为例,尝试一个可能的答案,如果这个答案符合题目条件,那么它肯定是“最小”(可行解),但不一定是“最大”(最优解),然后我们换个更大的可能答案,如果也符合条件,那这个新可行解就更优,不断重复即可。怎么找呢?这时就该二分上场了。

二分前提

1.答案区间上下限确定,即最终答案在哪个范围是容易知道的。

2.检验某值是否可行是个简单活,即给你个值,你能很容易的判断是不是符合题目要求。

3.可行解满足区间单调性,即若x是可行解,则在答案区间内x+1(也可能是x-1)也可行。

二分查找就是二分法的一个简单应用,它也符合二分的三个前提条件:

1.答案区间上下限:就是1到n

2.容易检验是否是答案:只要直接判断二分到的位置的数是否是要查找的数即可

3.区间具有单调性:序列已经是排过序的,明显具有单调性

以本题为例:

设 y=f(x)=ax^3+bx^2+cx+dy=f(x)=ax3+bx2+cx+d 。题目明确说根与根之差的绝对值 >=1 ,那反过来就是说间隔为1的区间内最多只有一个解。

假设|x2-x1|≤1,如果f(x2) × f(x1) < 0 则 x1和x2之间肯定有一个解,然后对这个区间进行二分查找答案。

对比二分法的三个要素均满足:

1.查找的区间限定在间隔为1的区间内(程序里到时枚举所有的区间)

2.对于二分到的数值,只要代入方程求解判断是否满足条件即可。注意实数是不能直接比较相等的,y是实数,不能写y==0,要写成|y|<1e-6 (注意:误差容许范围要根据题目调整,本题要求的精确度是小数点后4位,我们比较的精度要比这个更高才不会出错)

 3.x1到x2 之间的间隔0.0001的数明显是有序的;

 

【题目部分】

给出从小到大排列的n个不同数a[1]~a[n],试判断元素x是否出现在表中。如果在则输出"yes",否则输出“no”

 

输入

 

输入有三行。

第一行一个数n。(1 ≤ n ≤ 500000)

第二行有n个数。

第三行为要查找的数。

 

 

输出

 

如果在则输出"yes",否则输出“no”

输入样例 1                          输出样例1

6                      yes
1 2 3 4 5 6
3

 

【代码部分】AC代码

#include<bits/stdc++.h>

using namespace std;

int search(int key[],int low, int high,int k) //注意:high的值是数组的最后一个单元位置,不是数组元素个数 //key 源数据数组,假设已从小到大排序 。low:查找区域的前端点;high:查找区域的后端点;k:要查找的值

{

  int mid; if(low>high) //如果数组里没有要查找的数据,返回 -1

  return -1;

  else{

   mid =low+ (high-low) / 2; //先计算查找区域的中点位置。也有写成:mid = (low+high) / 2; 但如果low和high很大会有溢出int范围的风险

  if(key[mid]==k) //如果mid位置的值就是要查找的,就把mid的值返回

    return mid;

  if(k>key[mid]) //如果要找的值比mid位置的值大,那么就到后半部分查找

    return search(key,mid+1,high,k); /*在序列的后半部分查找*/

  else return search(key,low,mid-1,k); /*在序列的前半部分查找*/ } }

int main(){

  int n,a[1000000];

  cin>>n;

  for(int i=1;i<=n;i++){

    cin>>a[i];

  }

  int x;

  cin>>x;

  search(a,1,n,x);

  if(search(a,1,n,x)==-1)

  {

    cout<<"no";

  }else

    cout<<"yes";

}

【经典例题】

1.【二分】序列划分<OJ>

2.【NOIP2015年提高组题Day2】跳石头 (洛谷传送门)

3.【二分/分治】一元三次方程求解(2)<OJ>

4.[Median-dxz]御坂网络(Links)<OJ2756>

猜你喜欢

转载自www.cnblogs.com/Gavin-FJ/p/11281630.html