当前位置: 首页 > news >正文

查找算法--python

二分查找

一、概述

基于有序数组的一种查找算法,主要使用了分治的思想,在每次查找的过程后,都能缩小一半的搜索范围,比如在1到100内猜数字,在保险的情况下先说50,根据结果再分析范围是1到49、51到100还是就是50,可以大大的减少查询次数。

二、查找元素位置

步骤

  • 设置边界,一般left取0作为左边界,right取lenth - 1作为右边界
  • 计算mid = (left + right) // 2,查看中间值,并和target比较
  • 当nums[mid] < target时,说明目标值在中间值的右边,此时更新左边界为mid + 1
  • 当nums[mid] > target时,说明目标值在中间值的左边,此时更新右边界为mid - 1
  • 当nums[mid] = target时,说明找到目标值,返回mid值,也就是目标值的位置索引

例如–左闭右闭

def binary_search(nums: list[int], target: int):# 确定搜索边界left, right = 0, len(nums) - 1# 在右边界大于等于左边界时一直搜while left <= right:# 寻找中间值,这种写法是为了防止越界(在python中很少)mid = left + (right - left) // 2# 当中间值小于目标值时,左边界挪到中间值右边一位if nums[mid] < target:left = mid + 1# 当中间值大于目标值时,右边界挪到中间值左边一位elif nums[mid] > target:right = mid - 1# 找到目标值,直接返回位置else:return mid# 没找到就返回-1return -1

这种为区间左闭右闭的情况,一般来说,因为python中有很多“包左不包右”的情况,所以左闭右开的区间也很常见,当这种情况发生时,判断条件中的left=right就不可能发生了,会导致找不到目标值或者返回错误的目标,所以我们做一下修改。

例如–左闭右开

def binary_search(nums: list[int], target: int):# 设置边界为左闭右开left, right = 0, len(nums)# 左右边界不会相等,去除相等的条件while left < right:# 寻找中间值,这种写法是为了防止越界(在python中很少)mid = left + (right - left) // 2# 当中间值小于目标值时,左边界挪到中间值右边一位if nums[mid] < target:left = mid + 1# 当中间值小于目标值时,因为左边界取不到,左边界挪到中间值位置elif nums[mid] > target:right = mid# 找到后返回索引else:return mid# 没找到返回-1return -1

左开右闭也是同理,后续的区间范围要和开始的相同(闭或者开)。

三、查找元素插入位置

上述章节中,我们发现在元素不存在时,我们直接返回了-1,那么如果元素不存在后,我们现在想插入这个元素,该怎么用二分查找查到元素的插入位置呢。

此时我们也要分两种情况,有重复元素和没有重复元素的情况,其中无重复元素情况相较简单我们先分析。

1、无重复元素

首先最重要的是,在选择插入时,我们要找的索引是什么,此时有两个情况:

  • 元素中本身就包括target时:就插入到当前target的位置。
  • 元素中不包括target时:经过简单分析可以得出,插入的位置为首个比当前元素大的元素的位置(可能有点拗口),我们的目的就是去找到这个位置,后续插入该位置。
def binary_search_insert(nums: list[int], target: int):# 确认查找的左右边界left, right = 0, len(nums) - 1# 在右边界大于等于左边界时一直搜while left <= right:# 寻找中间值,这种写法是为了防止越界(在python中很少)mid = left + (right - left) // 2# 当中间值小于目标值时,左边界挪到中间值右边一位if nums[mid] < target:left = mid + 1# 当中间值大于目标值时,右边界挪到中间值左边一位elif nums[mid] > target:right = mid - 1# 找到了目标值else:return mid# 没找到,此时左边的边界就是我们要找的插入点return left

2、有重复元素

上述是无重复元素的情况,位置比较好找,就是首个大于当前元素的位置,但是当有重复元素的时候,没有办法去确认该元素有多少个,如果是直接插入的话,则找到就行,不过此时我们想要去插入到区间的最左边,此时我们需要找到首个小于target的位置。

def binary_search_insert(nums: list[int], target: int):# 确认查找的左右边界left, right = 0, len(nums) - 1while left <= right:# 寻找中间值,这种写法是为了防止越界(在python中很少)mid = left + (right - left) // 2# 当中间值小于目标值时,左边界挪到中间值右边一位if nums[mid] < target:left = mid + 1# 当中间值大于目标值时,右边界挪到中间值左边一位elif nums[mid] > target:right = mid - 1# 缩小右边界直到找到首个小于目标值的元素else:right = mid - 1# 此时左边的边界就是我们要找的最左边return left

四、查找元素边界

与上文类似,当我们插入一个值的时候,可能会出现该列表中已经有多个类似的值了,此时我们想要知道重复元素的区间范围,可以复用上述插入的思想。

查找右边界

def binary_search_right(nums: list[int], target: int):# 确认查找的左右边界left, right = 0, len(nums) - 1while left <= right:# 寻找中间值,这种写法是为了防止越界(在python中很少)mid = left + (right - left) // 2# 当中间值小于目标值时,左边界挪到中间值右边一位if nums[mid] < target + 1:left = mid + 1# 当中间值大于目标值时,右边界挪到中间值左边一位elif nums[mid] > target + 1:right = mid - 1# 缩小右边界直到找到首个小于目标值的元素else:right = mid - 1# 此时左边的边界就是我们要找的首个大于目标值的插入点index = left# 此时的左边界-1就是我们要找的目标值的右边界res = left - 1# 右边界不合法说明没有元素if res == -1 or nums[res] != target:return -1# 返回右边界return res

查找左边界

def binary_search_left(nums: list[int], target: int):# 确认查找的左右边界left, right = 0, len(nums) - 1while left <= right:# 寻找中间值,这种写法是为了防止越界(在python中很少)mid = left + (right - left) // 2# 当中间值小于目标值时,左边界挪到中间值右边一位if nums[mid] < target:left = mid + 1# 当中间值大于目标值时,右边界挪到中间值左边一位elif nums[mid] > target:right = mid - 1# 缩小右边界直到找到首个小于目标值的元素else:right = mid - 1# 此时左边的边界就是我们要找的目标值左边界index = left# 左边界不合法说明没有元素if index == len(nums) or nums[index] != target:return -1# 返回左边界return index

五、小结

二分查找的效率很高,时间复杂度为O(logn),而且该算法不需要额外的内存来做排序。

不过二分查找最大的弊端就是需要时有序的序列,而排序算法的复杂度通常都在O(nlogn)以上,反而比线性查找更慢;而且二分查找只能作用于数组,不适合用于链表当中。

哈希查找

该查找可以用力扣第一题来理解:https://leetcode.cn/problems/two-sum/description/

这题的常规思路肯定是直接两层遍历,枚举出符合条件的数字,但是时间复杂度为O(n²),当数据量很大时,时间会非常长,此时我们就可以用哈希查找去做优化。

众所周知,哈希是键值对相互对应的,此时我们建立一个哈希表,将值和索引存到 哈希表中,后续查找的时候就可以直接利用键值对的关系找到第二个值了。

def twoSum(nums: List[int], target: int) -> List[int]:# 列表长度lenth = len(nums)# 建立字典nums_dict = {}# 遍历列表for i in range(lenth):# 获得另一个值的是多少tmp = target - nums[i]# 如果另一个值在字典中,直接返回索引if tmp in nums_dict:return [nums_dict[tmp], i]# 如果没有就加新键值对nums_dict[nums[i]] = i# 没有合适的两个值return []

时间复杂度降到O(n),相对的空间复杂度从O(1)上升到O(n)。这就是典型的空间换时间,


http://www.mrgr.cn/news/27104.html

相关文章:

  • Makefile(超详细一文读懂)
  • 算法基础-扩展欧几里得算法
  • JS Web
  • python容器四之字典
  • GD - GD32350R_EVAL - PWM实验和验证3 - EmbeddedBuilder - 无源蜂鸣器 - 用PMOS来控制
  • 随机查询若干数据,并根据全部数据的点击量排序的核心代码
  • list和vector的区别
  • 在 Mac 中设置环境变量
  • 性能测试的复习4-数据库连接、控制器、定时器
  • Spring Cloud常见面试题
  • 初学者指南:如何在Windows 11中自定义任务栏颜色,全面解析!
  • 【C#生态园】从基础到深度学习:探索C#机器学习库
  • Stream流的思想和获取Stream流
  • 共享单车轨迹数据分析:以厦门市共享单车数据为例(四)
  • 加速开发体验:为 Android Studio 设置国内镜像源
  • JavaScript --函数的作用域(全局和局部)
  • 测试质量体系的风险评估和应对措施有哪些
  • GO Server-Sent Events (SSE)
  • Cache Aside pattern
  • USB组合设备——鼠标+键盘(两个接口实现)