数据结构与算法7:递归&二分查找

递归(recursion)

  • 递归需要满足的三个条件:
    1. 一个问题的解可以分解为几个子问题的解
    2. 这个问题与分解得到的子问题,除了数据规模不同,求解思路完全一样
    3. 存在递归终止条件
  • 递归代码的编写:
    1. 找到将大问题分解为小问题的规律,基于此写出递推公式
    2. 推敲终止条件
    3. 将两者结合,翻译为代码
  • 两个注意点:
    1. 警惕堆栈溢出:通过限制递归调用的最大深度来解决
    2. 警惕重复计算:通过使用一个散列表来保存已经求解过的值
  • 复杂度分析:
    • 时间:当递归代码中调用的函数数量较大时,会积累成一个可观的时间成本
    • 空间:递归调用一次就会在内存栈保存一次现场数据,需要额外考虑这部分的开销
  • 递归与非递归
    • 优点:递归代码的表达力很强,写起来非常简洁
    • 不足:空间复杂度高,有堆栈溢出的风险,存在重复计算,过多的函数调用比较耗时

二分查找

二分查找针对有序的数列集合,查找思想类似于分治思想,每次都和区间的中间元素对比,将要查找的区间缩小为原来的一半,直到找到要查找的元素,或者区间缩小为0。
时间复杂度:O(logn)(堆和二叉树的操作时间复杂度也是O(logn))

二分查找的递归实现

def binary_search(nums, n, value):
    """递归实现在没有重复元素的数组中查找给定值,找到则返回元素下标,否则返回-1"""
    return _binary_search_v2(nums, 0, n-1, value)


def _binary_search(nums, low, high, value):
    """二分查找实现函数,在指定区间查找给定元素"""
    if low > high:
        return -1
    mid = low + ((high - low) >> 1)
    if nums[mid] == value:
        return mid
    elif nums[mid] < value:
        return _binary_search(nums, mid+1, high, value)
    else:
        return _binary_search(nums, low, mid - 1, value)

二分查找的非递归实现

def binary_search_v1(nums, n, value):
    """循环实现在没有重复元素的数组中查找给定值,找到则返回元素下标,否则返回-1
    :param nums: 待查找的列表
    :param n: 列表元素个数
    :param value: 要查找的元素
    :return:
    """
    low, high = 0, n-1
    while low <= high:
        # mid = int((high+low)//2)  # high+low可能溢出,将除法运算转换为移位运算
        mid = low + ((high - low) >> 1)
        if nums[mid] == value:
            return mid
        elif nums[mid] < value:
            low = mid + 1
        else:
            high = mid - 1
    return -1

适用场景

  • 数据使用顺序表结构存储,保证随机访问
  • 数据必须是有序的。对于无频繁插入、删除操作的静态数据可以一次排序多次查找;对于动态变化的数据集合,不适用
  • 数据量太小可以使用顺序遍历查找,数据量太大使用二分查找需要连续内存空间

相关的面试题

  • 题目:1000万个整数数据,每个占8字节,设计数据结构和算法,快速判断某个整数是否出现在这1000万个数据中?
    将1000万个8字节数据存储在数组中占用内存80MB左右,然后对1000万数据从小到大排序,再使用二分查找算法。
    虽然大多数情况下,用二分查找可以解决的问题,用散列表、二叉树这些支持快速查找的动态数据结构都可以解决,但是散列表和二叉树都需要比较多的额外的内存空间,而二分查找底层依赖的是数组,除了数据本身之外,不需要额外存储其他信息,是最省内存空间的快速查找算法。
  • 题目:二分法求一个数的平方根,精确到小数点后六位
def sqrt(num, n=6):
    """求一个数的平方根,精确到小数点后n位
    先确定整数部分,再确定小数部分,小数部分每次确定一位,最后四舍五入
    :param num:
    :param n:
    :return:
    """
    # 保留小数点后n+1位
    for i in range(0, n+2):
        if i == 0:
            low, high = 0, num
        else:
            low, high = round(res+0.0/(10**i),i), round(res+10.0/(10**i),i)
        res= round(_sqrt(num, low, high, i),i)
    # 对最后一位四舍五入
    return round(res, n)


def _sqrt(num, low, high, i):
    """对[low, high]范围内的i位数二分查找确定num的平方根
    :param num: 目标数
    :param low: 二分查找下界
    :param high: 二分查找上界
    :param i: 查找位数,为0时表示整数部分
    :return:
    """
    while low <= high:
        if i == 0:
            mid = low + ((high - low) >> 1)
        else:
            mid = round(low + ((high - low)/2), i)
        if mid ** 2 == num:
            return mid
        elif mid**2 < num and (mid+1/(10**i))**2 > num:
            return mid
        elif mid**2 < num:
            low = round(mid+1/(10**i),i)
        elif mid**2 >num and (mid-1/(10**i))**2 < num:
            return mid-1/(10**i)
        else:
            high = round(mid-1/(10**i),i)
  • 题目:二分查找使用链表存储的数据的时间复杂度
    假设链表长度为n,第一次指针移动n/2次,第二次移动n/4次,最多移动logn次,指针移动次数(查找次数)最多为O(n),和遍历查找相同

二分查找的变形问题

  • 查找第一个值等于给定值的元素
def binary_search(nums, n, value):
    """
    在包含重复元素的数组中查找第一个值等于给定值的元素,没有则返回-1
    :param nums:
    :param value:
    :return:
    """
    low, high = 0, n-1
    while low < high:
        mid = low + ((high - low) >> 1)
        if nums[mid] >= value:
            high = mid
        else:
            low = mid + 1
    if nums[low] == value:
        return low
    else:
        return -1
  • 查找最后一个值等于给定值的元素
def binary_search(nums, n, value):
    """
    在包含重复元素的数组中查找最后一个值等于给定值的元素,没有则返回-1
    :param nums:
    :param value:
    :return:
    """
    low, high = 0, n-1
    while low < high:
        mid = low + ((high - low + 1) >> 1)  # 此处加1是为了当low和high相邻时mid取到high
        if nums[mid] <= value:
            low = mid
        else:
            high = mid - 1
    if nums[low] == value:
        return low
    else:
        return -1
  • 查找第一个大于等于给定值的元素
def binary_search(nums, n, value):
    """
    在包含重复元素的数组中查找第一个值大于等于给定值的元素
    :param nums:
    :param value:
    :return:
    """
    low, high = 0, n-1
    while low < high:
        mid = low + ((high - low) >> 1)
        if nums[mid] >= value:
            high = mid
        else:
            low = mid + 1
    return low
  • 查找最后一个小于等于给定值的元素
def binary_search(nums, n, value):
    """
    在包含重复元素的数组中查找最后一个值小于等于给定值的元素
    :param nums:
    :param value:
    :return:
    """
    low, high = 0, n-1
    while low < high:
        mid = low + ((high - low + 1) >> 1)  # 此处加1是为了当low和high相邻时mid取到high
        if nums[mid] <= value:
            low = mid
        else:
            high = mid - 1
    return low
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值