【数据结构】二分查找

目录

1、定义

2、解题模板

版本1

版本2

如何决定check()函数?

3、相关题目

162. 寻找峰值

153. 寻找旋转排序数组中的最小值

4、位运算

5、其他

python 二分查找包bisect: python 模块bisect


1、定义

二分查找又称折半查找,它是一种效率较高的查找方法。(要已经排好序!!!

二分查找要求:线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构。不妨设有序表是递增有序的。

二分查找只适用顺序存储结构

2、解题模板

二分模板一共有两个,分别适用于不同情况。

算法思路:假设目标值在闭区间 [l, r] 中, 每次将区间长度缩小一半,当 l = r 时,我们就找到了目标值。

版本1

当我们将区间 [l, r] 划分成 [l, mid] 和 [mid + 1, r] 时,其更新操作是 r = mid 或者 l = mid + 1 ;,计算mid时不需要加1。

C++ 代码模板:

int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

版本2

当我们将区间 [l, r] 划分成 [l, mid - 1] 和 [mid, r] 时,其更新操作是 r = mid - 1 或者 l = mid ;,此时为了防止死循环,计算mid时需要加1。

C++ 代码模板:

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

其实模板1和模板2本质上是根据代码来区分的,而不是应用场景。

如果写完之后发现是l = mid,那么在计算mid时需要加上1,

否则如果写完之后发现是r = mid,那么在计算mid时不能加1。

如何决定check()函数?

假设有一个总区间,经由我们的 check 函数判断后,可分成两部分,
这边以o作 true,.....作 false 示意较好识别

如果我们的目标是下面这个v,那么就必须使用模板 1(r = mid)

................vooooooooo

假设经由 check 划分后,整个区间的属性与目标v如下,则我们必须使用模板 2(l = mid)

oooooooov...................

所以下次可以观察 check 属性再与模板1 or 2 互相搭配就不会写错啦

ps: v 的属性,本身也要是 true,也就是 check 为 true 的左边界或是右边界(前提是目标一定是找得到的,不然跳出回圈后,必须加上额外判断)。

来源:AcWing@yxc

3、相关题目

应用版本一:LeetCode 275. H-Index II;
应用版本二:LeetCode 69. Sqrt(x) ;

162. 寻找峰值

class Solution:
    def findPeakElement(self, nums: List[int]) -> int:
        l = 0; r = len(nums) - 1
        while l < r:
            mid = l + r >> 1
            if nums[mid] < nums[mid + 1]: # 若当前点比右边小,则峰值在右边
                l = mid + 1
            else:
                r = mid 
        return r

思路:
由于假设左右端点的值为负无穷,所以每次在区间中找出一点,
其到右端点要么单调递减,要么存在峰值
左端点到该点要么单调递增,要么存在峰值
步骤:
我们每次找出区间中间的点,判断它与它右边的数的大小关系(也可比较与左边的数)
若nums[mid] < nums[mid+1]说明存在单调递增,则可以判断Mid右边的区间必存在峰值,
因为只有右边是单调递减才会不存在峰值
每次筛选出的是一定存在峰值的区间,不代表另一半就没有峰值哦
若nums[mid] >= nums[mid+1],不代表右边一定没峰值,只是代表左边一定有峰值
所以为了节省时间快速找到任意一个峰值,我们把右指针 r = mid 

LeetCode 162. 寻找峰值-Python-无两段性性质的区间二分 - AcWing

153. 寻找旋转排序数组中的最小值

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

# mid < right 表面右边有序 否则左边有序 那么mid > right 则表明最小数肯定在mid 和 right之间


# 套模板
def minArray(self, numbers) -> int:
    n = len(numbers)-1
    while(n>0 and numbers[n]==numbers[0]): n-=1
    # 最后比第一个值大,则一定递增
    if numbers[n] >= numbers[0]:return numbers[0]
    l, r = 0, n
    while(l<r):
        mid = (l+r)//2
        if numbers[mid]<numbers[0]: r=mid
        else:
            l = mid+1
    return numbers[l]

4、位运算

位运算符——左移、右移

>> :右移 最高位是0,左边补齐0;最高为是1,左边补齐1

<<  :左移 左边最高位丢弃,右边补齐0

>>>:无符号右移 无论最高位是0还是1,左边补齐0

在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方

右移一位相当于除2,右移n位相当于除以2的n次方。

12>>1   结果:6    12/2^1
12>>2   结果:3    12/2^2

12<<1     结果 :24              12x2^1
12<<2     结果 :48    12x2^2

5、其他

优点

   二分查找的时间复杂度为O(logn),远远好于顺序查找的O(n)。

平均查找长度ASL

\参考:

二分查找(Binary Search)

backlog

# 要求序列有序

def binary_search(alsit,item):
    """二分查找,递归"""
    n = len(alsit)
    if n>0:
        mid = n//2
        if alsit[mid] == item:
            return True
        elif item<alsit[mid]:
            return binary_search(alsit[:mid],item)
        else:
            return binary_search(alsit[mid+1:],item)
    return False

def binary_search_2(alist,item):
    """二分查找,非递归"""
    n = len(alist)
    first = 0
    last = n-1
    while first<=last:  # 当子表长度大于等于1的时候进行循环!!
        mid = (first + last) // 2
        if alist[mid]==item:
            return True
        elif item<alist[mid]:
            last=mid-1
        else:
            first=mid+1
    return False
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值