• 数据结构 二分查找1


    包括以下内容:

    1. 二分查找key在数组中的位置
    2. 二分查找数组中第一个大于或等于key的位置
    3. 二分查找数组中第一个大于key的位置

    变量解释:int[] arr1; 记录查找表,所有元素都是唯一的

                      int[] arr2; 记录查找表,元素不唯一

     

    测试用例:

      0 1 2 3 4 5 6 7 8
    arr1[] 01 05 07 09 10 15 18 22 25
    arr2[] 01 01 07 07 15 15 15 22 25

    一. 查找key在数组中的位置, 查找不成功则返回-1;

    迭代实现:

    1 int binary_search1(int arr[], int low, int high, int key){
    2     while(low<=high){
    3         int mid = low + (high-low)/2;
    4         if(arr[mid]<key) low=mid+1;
    5         else if(arr[mid]>key) high=mid-1;
    6         else return mid;
    7     }
    8     return -1;
    9 }

    归实现:

    1 int binary_search(int arr[], int low, int high, int key){
    2     int mid = low+(high-low)/2;
    3     if(low>high) return -1;
    4     if(arr[mid]<key)  return binary_search(arr, mid+1, high, key);
    5     if(arr[mid]>key)  return binary_search(arr, low, mid-1, key);
    6     return mid;
    7 }

    这里对递归实现,做一定的解释:

    首先这个函数的功能是在查找表arr1[]中查找key所在的位置, 如果查找成功则返回所在位置, 否则返回-1,标志未查找成功; 二分查找的前提的查找表有序,这里以查找表递增为例实现二叉查找;

    每次调用函数,是对整个数组查找, 需要查找表数组arr, 查找范围的下限low,和上限high, 以及要查找的值key; 为了统一,我们这里把上限+1,后面做解释

    二分查找的思路:先拿key和查找表最中间的值比较:

      1.如果比中间值大,则key只可能在查找表的右半边, 在查找表的右边进行二叉查找

      2. 如果key比中间的值小,那么key只可能在查找表的左半边,在查找表的左边进行二叉查找

      3.如果相等, 则找到key所在key的位置 

    以查找7为例, 

    0 1 2 3 4 5 6 7 8 9  
    1 5 7 9 10 15 18 22 25    
    low       mid         high 7比10大,在查找表左边进行二叉查找, high=mid-1
    low mid   high             7比5大,在查找表的右边进行二叉查找, low=mid+1
      low mid high             找到7,返回7所在位置

     

     

     

     

    有几个需要注意的地方:

    1. while的循环条件:应该是low<=high,  而不是low<high;
    2. 每次循环如果没有找到key所在的位置,要修改low=mid-1,或者high=mid+1; 不能是low=mid, 或者是high=mid,否则会导致死循环
    3. 只适用于递增的查找表

    二叉查找,相当于在一颗平衡二叉树中进行查找, 平衡二叉树的高度h=[logn]+1, n是查找表的长度, 所以使用二叉树查找值得时候,在O(logn)的时间内就能找到所需要查找的值,及时查找失败, 比较次数也不会超过查找表对于的平衡二叉树的深度

    二叉查找并不能保证比顺序查找的更优 , 对于查找概率相同的的key来说二叉树更优,当查找表前面的值查找频率较高的时候,顺序查找的效率可能比二叉查找更优

    二. 查找表中第一个大于等于key的位置

    1 int lower_bound(int arr[], int low, int high, int key){
    2     while(low<high){
    3         int mid = low + (high-low)/2;
    4         if(key>arr[mid]) low=mid+1;
    5         else  high=mid;
    6     }
    7     return low;
    8 }

    函数解释:在不减的查找表里面找第一个大于或者等于key的位置, 如果key在查找表中不存在, 返回的是key在数组中应该在的位置, 比如在1,3,5中查找2, 返回的值是1, 意思就是如果查找表中有2这个值,他的位置应该在1这个位置, 如果key在查找表中, 返回的值就是key所在的位置; 解释一下上面的上限为什么要+1,任然以1,3,5为例,如果在表中查找8,传入2作为上限, 返回的值就是2,但这是错误的,8的位置应该在3;所以上限加一在于解决查找值key比查找表中所有元素还大的情况, 而对其他值得查找不会有影响;

    这个函数和上面的函数很相似,整体的框架类似, 但是存在一些细微的差别:

    1. while的循环判断条件不同
    2. high的修改条件不同
    3. 返回值不同

    下面对这三点做出解释

    1. 在二分查找时,有一个隐含条件是查找值key必须在查找表中,当low==high以及确定了一个唯一的位置,但是需要验证该位置的值是否等于key;但是在这个函数中,我们要找的是第一个大于或等于key的位置,如果key存在于查找表中,那么当low==hight时,查找表的值一定为key,不存在也没关系,该位置即为key在查找表中应该所在的位置
    2. 因为我们要找的是第一个大于或者等于key所在位置,那么当arr[mid]<key时, key的位置肯定在mid的右边,不可能在mid上,因而low=mid+1; 当arr[mid]>key时,key的位置可能在mid的左边也可能就在mid上,因为要找的是大于或等于key的值, 当arr[mid]==key的时候, key的位置就在mid上, 让high=mid,经过几次迭代就能让low==high,从而退出循环, 可以发现后面两种情况是可以合并的, 将其合并在一起,让代码精简一些
    3. 如果你手写实现一下该迭代过程,就会发现终止循环的的情况一定是low==high,这种情况下确定了一个唯一的位置, 返回low和high其实都一样;因为每一次迭代low的最大值即为low=[(low+high)/2]+1, 改值一定是不大于high的当low等于high退出循环,当low小于high,进行下一次迭代

    以查找不存在的8为例, 正确的位置应该在4;

    0 1 2 3 4 5 6 7 8 9  
    1 1 7 7 15 15 15 23 25    
    low        mid          high 8小于15,在mid左边进行查找, high=mid
     low   mid   high           8大于7,在mid右边进行查找,low=mid+1
          low, mid high           8大于7,在mid右边进行查找,low=mid+1
            low, high           low<high条件不满足,返回low找到key应该在的位置

    三.查找第一个大于key所在的位置

    int upper_bound(int arr[], int low, int high, int key){
        while(low<high){
            int mid = low + (high-low)/2;
            if(key>=arr[mid]) low=mid+1;
            else high=mid;
        }
        return low;
    }

    路和查找第一个大于或等于key的位置的函数一样, 这里只需要把等于的条件放在左边即可; 这里不再做解释

    lower_bound和upper_bound组合使用能很方便的得到查找表中所有值等于key的左闭右开区间

    通过相同的思路能能实现找到第一个小于或等于key的位置, 第一个等于key的位置;

  • 相关阅读:
    loj6158 A+B Problem (扩展KMP)
    2017CodeM初赛B场
    Codeforces Round #421(div 2)
    CF821E(多次矩阵快速幂)
    Codechef-ANCESTOR(树套树/CDQ分治)
    Codechef-BLACKCOM(树形背包dp)
    Codechef-CHEFPRAD(找事件点+贪心)
    洛谷 p3391
    luogu p3369
    LOJ10082
  • 原文地址:https://www.cnblogs.com/mr-stn/p/9265484.html
Copyright © 2020-2023  润新知