• 二分查找相关问题总结


    参考二分查找各种情况大总结

    二分查找原始版

    在有序数组中查找某个数,找到返回数的下标,存在多个返回任意一个即可,没有返回-1。所有程序采用左右均为闭区间,即函数中n为最后一个元素下标,而不是元素个数。典型代码如下:

    public int binarySearch(int[] a, int n, int key){
    		//n + 1 个数
    		int low = 0;
    		int high = n;
    		int mid = 0;
    		while(low <= high) {
    			mid = low + ((high-low) >> 1);
    			if(key == a[mid]) {
    				return mid;
    			} else if(key < a[mid]) {
    				high = mid - 1;
    			} else {
    				low = mid + 1;
    			}
    		}
    		return -1;
    	}
    

    注意这里用到的是后面要总结的左闭右闭的方式进行的查找,如何看是左闭右开还是左闭右闭的查找方式呢,首先high和low的移动方式是判断的先绝条件,对于左闭右闭的方式low和high移动时一定时mid+1或者mid-1,但是对于左闭右开的方式,high移动时一定是mid而不做加减,这是因为high移动时我们不能确定target的前一个位置的元素一定时不属于查找区间的。顺便说一下:如果时左闭右闭的那么high初始化只能为length-1,但是如果是左闭右开的那么high必须初始化为length。

    查找第一个大于等于某个数的下标

    例:int[] a = {1,2,2,2,4,8,10},查找2,返回第一个2的下标1;查找3,返回4的下标4;查找4,返回4的下标4。如果没有大于等于key的元素,返回-1。下面是代码,改动只有两处:

    public int firstGreatOrEqual(int[] a, int n, int key){
    		//n + 1 个数
    		int low = 0;
    		int high = n;
    		int mid = 0;
    		while(low <= high) {
    			mid = low + ((high-low) >> 1);
    			if(key <= a[mid]) {
    				high = mid - 1;
    			} else {
    				low = mid + 1;
    			}
    		}
    		return low <= n ? low : -1;
    	}
    
    

    解释:

    1、条件为key<=a[mid],意思是key小于等于中间值,则往左半区域查找。如在 {1,2,2,2,4,8,10}查找2,第一步,low=0, high=6, 得mid=3, key <= a[3],往下标{1,2,2}中继续查找。

    2、终止前一步为: low=high,得mid = low,此时如果key <= a[mid],则high会改变,而low指向当前元素,即为满足要求的元素。如果key > a[mid],则low会改变,而low指向mid下一个元素。

    3、如果key大于数组最后一个元素,low最后变为n+1,即没有元素大于key,需要返回 -1。

    查找第一个大于某个数的下标

    例:int[] a = {1,2,2,2,4,8,10},查找2,返回4的下标4;查找3,返回4的下标4;查找4,返回8的下标5。如果没有大于key的元素,返回-1。

    如下是代码,与上面大于等于某个数仅判断一个符号不同:

    public int firstGreat(int[] a, int n, int key){
    		//n + 1 个数
    		int low = 0;
    		int high = n;
    		int mid = 0;
    		while(low <= high) {
    			mid = low + ((high-low) >> 1);
    			if(key < a[mid]) {
    				high = mid - 1;
    			} else {
    				low = mid + 1;
    			}
    		}
    		return low <= n ? low : -1;
    	}
    

    上面的基础形式可以有下面的集中扩展:

    (1) 查找数组中某个数的位置的最小下标

    可以先查找数组中第一个大于等于某个数的位置,然后做一个判断,如果等于这个数直接返回下标,否则就返回-1

    (2) 查找数组中某个数的位置的最大下标

    可以先查找数组中第一个大于这个数的位置,然后将这个数和这个位置上的前一数进行比较,如果相等返回前一个位置,否则就返回-1。

    (3) 查找数组中小于某个数的最大下标

    可以先查找第一个大于等于这个数的下标,如果前一个位置有效,返回前一个位置,否则返回-1

    (4) 查找数组中某个数的出现次数

    首先用(1)求下界,然后用(2) 求上界,如果上界和下界都存在二者的差值+1就是出现的次数

    参考 二分查找学习札记

    二分查找的边界控制

    二分查找算法的边界,一般来说分两种情况,一种是左闭右开区间,类似于[left, right),一种是左闭右闭区间,类似于[left, right].需要注意的是, 循环体外的初始化条件,与循环体内的迭代步骤, 都必须遵守一致的区间规则,也就是说,如果循环体初始化时,是以左闭右开区间为边界的,那么循环体内部的迭代也应该如此.如果两者不一致,会造成程序的错误.

    # 两种闭合方式程序的对比
    def left_in_right_in(arr, length, target):
        lo, hi = 0,length-1
        while lo<=hi:
            mid = lo + (hi-lo)>>1
            if arr[mid] in the left of target interval:
                lo = mid+1
            elif arr[mid] in the right of target interval:
                hi = mid-1
            else: # arr[mid] in the target interval
                return mid
        return # 只能找到或者是找不到
    
    def left_in_right_out(arr, length, target):
        lo , hi = 0,length
        while lo < hi:
            mid = lo + (hi-lo)>>1
            if arr[mid] in the left of target intervel:
                lo = mid+1
            elif arr[mid] in the right of target interval:
                hi = mid
            else: # arr[mid] in the target interval
                hi = mid
        return {
            lo: 'the left margin of the target interval'
            hi: 'it must equal to lo, its meaning equal to lo'
        }
    

    现在来总结一条规律:

    两种闭合方式需要仔细鉴别,如果使用左闭右闭的方式那么循环跳出的时机一定是lo>hi的时侯,如果使用左闭右开的方式那么循环跳出的时机一定是lo=hi的时侯,这里可以通过数学上当搜索区间中没有元素的时机条件来辅助理解。

    理解两种闭合方式的等价性

    # 搜索第一个等于target的元素的位置
    def left_in_right_in(arr, length, target):
        lo, hi = 0,length-1
        while lo<=hi:
            mid = lo + ((hi-lo)>>1)
            if arr[mid] < target:
                lo = mid+1
            elif arr[mid] > target:
                hi = mid-1
            else: # arr[mid] in the target interval
                hi = mid-1
        return lo if lo<length and arr[lo]==target else -1
    
    def left_in_right_out(arr, length, target):
        lo , hi = 0,length
        while lo < hi:
            mid = lo + ((hi-lo)>>1)
            if arr[mid] < target:
                lo = mid+1
            elif arr[mid] > target:
                hi = mid
            else: # arr[mid] in the target interval
                hi = mid
        return lo if lo < length  and arr[lo]==target else -1
    
    size = 50
    import random
    for i in range(100000):
        arr_length = int(random.random()*(size+1))
        arr = []
        for i in range(arr_length):
            arr.append(random.randint(0,100))
        target = random.randint(0,1000)
        arr.sort() # 所有二分查找的变体都要求有序性
        a=left_in_right_in(arr,arr_length,target)
        b=left_in_right_out(arr,arr_length,target)
        if a!=b :
            print(arr,target)
            print(a,b)
            break
    
  • 相关阅读:
    html file 表单样式(css过滤器实现)
    网页a标签
    Criteria示例
    数类型转换顺序
    Mysql命令行添加用户
    MySQL中导出用户权限设置的脚本
    mysql 命令行登录详解
    mysql的auto-rehash简介
    UltraEdit中的特殊字符
    ps aux详解(进程状态说明)
  • 原文地址:https://www.cnblogs.com/ZeroTensor/p/10550642.html
Copyright © 2020-2023  润新知