数据结构与算法之二分法

数据结构与算法之二分法

万物皆可分

 二分法应该是我们熟悉的内容,因为在初高中的数学课堂中均会设计到二分的思想。比如,我们求某一无理数的近似值,就是通过二分法。比如求 2 \sqrt{2} 2 的近似值,范围在[1,2]内。显然我们可以先取中点3/2,显然3/2的平方大于2,故在取区间[1,3/2]然后依次操作,直至区间长度达到所需的精度。

double eps=1e-5;
int f(double mid){
  return mid*mid;
}
int two_find(){
  double left=1;
  double right=2;
  double mid;
  while(right-left<eps){//达到所需的精度时就退出
      mid=(right-left)/2;
      if(f(mid)>2){
          right=mid;//在左侧的区间进行逼近
      }
      else{
          left=mid;//在右侧的区间进行逼近
  
      return mid;  
  }
}

 延续这一思路,我们在查找某一特定值是时候可以用上这一算法的思想。
例如:现有一个递增的整数序列,我们现在想知道这个整数的序列中是否存在一个值x,若存在则返回它所在的位置。
分析:我们当然可以将这个数组遍历一遍,但是时间复杂度是O(n)。我们学数据结构与算法的意义就是用简单、可行的方法解决一个问题。现在,我们要想想能否一个方法将时间复杂度再降低。联系今天介绍的内容,我们可以尝试用二分查找的方法,就是第一个例子一样,我们将每次查找的区间范围减少一半。
总的来说,O(n)的方法就是从头一个个查找,而二分查找就是先从序列的中间位置进行查找,然后再确定一个小区间,然后在折半。所以它的时间复杂度就是O(logn)。另外注意的是,查找的区间应该是有着一定的顺序的,比如递增或递减。如果序列是无顺序的无规律的,那么就无法使用二分查找,只能老老实实的运用暴力搜索了。
例子:一组递增的序列,查找其中是否有x,若有则返回其位置。

int a[10];
int left=0;
int right=9;
int x;
int two_find(){
    while(lef<=right){
        int mid=(left+right)/2;
        if(mid==x){
            return mid;// 查找成功,返回位置。
        }
        if(mid>x){
            right=mid+1;//在左区间
        }
        if(mid< x){
            left=mid+1;// 在右区间
        }
    }
    return -1;
}

推广(1):现在我不满足于找到x了,而是尝试找到序列中第一个大于等于x的元素位置。 注意:该序列中有重复的元素,且递增。
分析:其实我们得思考多种情况。
一是:该序列存在一个x。
二是,该序列中存在多个x,那么我们就得寻找第一个x的位置。二是,该序列中不存在x,但是x值位于某两个x值的之间,显然我们得返回后一个值,作为找到了第一个大于x的元素。三是:该序列不存在,且x大于序列中的任何一个元素值。
所以我们得在二分查找的代码基础上做一些改动。首先,当f(mid)==x时,不能直接返回mid值,因为序列中可能会有多个x值,你无法保证返回的mid值就是第一个x的位置。所以,这时候第一个x肯定在左区间,这时right=mid。不能是right=mid-1,可能会错过第一个x。
故当f(mid)>=x时。所以right=mid。
相应的,当f(mid)<x时,直接left=mid+1。程序并且返回left,因为此时的left值肯定是满足要求。

int a[10];
int left=0;
int right=9;
int x;
int two_find(){
   while(lef<right){//当left与right相邻时,mid取得是left,故当right==left时,仍得运行一次,用以left+1
       int mid=(left+right)/2;
       
       if(mid>=x){
           right=mid;//在左区间
       }
       if(mid< x){
           left=mid+1;// 在右区间
       }
   }
   return left;
}

推广(2):仍是同一个序列,但是我们要的是第一个大于x的元素的位置。
分析:相当于求出x紧邻后边的元素。所以如果有重复x值时,且当a[mid]==x时,我们应该将mid不断后移才能找到符合条件的。

int a[10];
int left=0;
int right=9;
int x;
int two_find(){
   while(lef<right){//当left与right相邻时,mid取得是left,故当right==left时,仍得运行一次,用以left+1
       int mid=(left+right)/2;
       
       if(mid>x){
           right=mid;//在左区间
       }
       if(mid< =x){
           left=mid+1;// 在右区间
       }
   }
   return left;
}

 木棒切割问题:给出N段木棒,长度均已知,现在希望通过切割它们得到至少K段长度相等(长度为整数)木棒,问:这些长度相等的木棒的长度最大值是?
分析:假设按长度为x来切割,得到了a段那么目前我们可以知道x越小我们就可以得到越短的段数。所以我们依次可以通过的对x进行尝试,直到满足条件的a。但是尝试也是要遵循一定的章法,联系上文,我们可以采用二分法来解决问题。
 首先我们先找寻x的可行区间,为[1,maxn],其中maxn是给定木棒长度的最大值。然后进行二分,得到mid,接着我们计算此时的会切割成多少段,若此时的段数a小于k,则我们应该去往左区间继续寻找。当大于k时,我们去右区间寻找。我们必须考虑当等于k时,我们不能直接返回了此时的x,因为会有连续几个x值,都会使得这些木棒被切割为k段,我们要找最大的x值,即这些x中的最后(右边末尾的那个),所以我们可以参考上文“寻找第一个大于x的位置”,只不过最后我们得往后退一个才是满足条件的临界长度。
代码如下:

int wud[n];//存储每根木棒的长度
int k;//题目要求的段数
int max_l;//理论上可以满足要求的最大切割长度
int sum=0;
for(int i=0;i<k;i++){
    sum+=wud[i];
}
max_l=sum/k;
int right,left;
int ans;
right=1;//切割长度的区间
left=max_l;
while(right<left){
    int cnt=0;
    mid=(right+left)/2;
    for(int i=0;i<k;i++){
        cnt+=wud[i]/mid;
    }
    if(cnt<k){//此时切割的长度过大
        right=mid;
    }
    if(cnt>=k){//此时切割的长度过小
        left=mid+1;
    }

}
ans=left-1;//得往后退一个,才是临界长度。

  总结:二分法的应用是有着条件。即一是,我们得有一个确切的区间,这样我们才能进行查找。二是,我们得有个查找的条件,根据这个条件我们能不断的修正我们查找的范围,这样最后才能找到或者无限的逼近最终的答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值