网站建设全网推广小程序,装潢设计培训班学费多少钱,公司培训,杭州餐饮网站建设搜索
二分查找
二分查找是一种基于分治策略的高效搜索算法。它利用数据的有序性#xff0c;每轮减少一半搜索范围#xff0c;直至找到目标元素或搜索区间为空为止。 Question#xff1a; 给定一个长度为n的数组 nums #xff0c;元素按从小到大的顺序排列#xff0c;数组…搜索
二分查找
二分查找是一种基于分治策略的高效搜索算法。它利用数据的有序性每轮减少一半搜索范围直至找到目标元素或搜索区间为空为止。 Question 给定一个长度为n的数组 nums 元素按从小到大的顺序排列数组不包含重复元素。请查找并返回元素 target 在该数组中的索引。若数组不包含该元素则返回 −1 双闭区间
如下图所示我们先初始化指针i0 和 jn−1 分别指向数组首元素和尾元素代表搜索区间 [0,n−1] 。请注意中括号表示闭区间其包含边界值本身。
接下来循环执行以下两步。 计算中点索引 m⌊(ij)/2⌋ 其中 ⌊⌋ 表示向下取整操作。 判断 nums[m] 和 target 的大小关系分为以下三种情况。 当 nums[m] target 时说明 target 在区间 [m1,j] 中因此执行 im1 。 当 nums[m] target 时说明 target 在区间 [i,m−1] 中因此执行 jm−1 。 当 nums[m] target 时说明找到 target 因此返回索引 m 。
若数组不包含目标元素搜索区间最终会缩小为空。此时返回 −1 。 值得注意的是由于 i 和 j 都是 int 类型因此 ij 可能会超出 int 类型的取值范围。为了避免大数越界我们通常采用公式 m⌊i(j−i)/2⌋ 来计算中点。
Python
def binary_search(nums: list[int], target: int) - int:二分查找双闭区间# 初始化双闭区间 [0, n-1] 即 i, j 分别指向数组首元素、尾元素i, j 0, len(nums) - 1# 循环当搜索区间为空时跳出当 i j 时为空while i j:# 理论上 Python 的数字可以无限大取决于内存大小无须考虑大数越界问题m (i j) // 2 # 计算中点索引 mif nums[m] target:i m 1 # 此情况说明 target 在区间 [m1, j] 中elif nums[m] target:j m - 1 # 此情况说明 target 在区间 [i, m-1] 中else:return m # 找到目标元素返回其索引return -1 # 未找到目标元素返回 -1Go
/* 二分查找双闭区间 */
func binarySearch(nums []int, target int) int {// 初始化双闭区间 [0, n-1] 即 i, j 分别指向数组首元素、尾元素i, j : 0, len(nums)-1// 循环当搜索区间为空时跳出当 i j 时为空for i j {m : i (j-i)/2 // 计算中点索引 mif nums[m] target { // 此情况说明 target 在区间 [m1, j] 中i m 1} else if nums[m] target { // 此情况说明 target 在区间 [i, m-1] 中j m - 1} else { // 找到目标元素返回其索引return m}}// 未找到目标元素返回 -1return -1
}时间复杂度 O(logn) 在二分循环中区间每轮缩小一半循环次数为 log₂n 。
空间复杂度 O(1) 指针 i 和 j 使用常数大小空间。
左开右闭
除了上述的双闭区间外常见的区间表示还有“左闭右开”区间定义为 [0,n) 即左边界包含自身右边界不包含自身。在该表示下区间 [i,j] 在 ij 时为空。可以基于该表示实现具有相同功能的二分查找算法。
Python
def binary_search_lcro(nums: list[int], target: int) - int:二分查找左闭右开# 初始化左闭右开 [0, n) 即 i, j 分别指向数组首元素、尾元素1i, j 0, len(nums)# 循环当搜索区间为空时跳出当 i j 时为空while i j:m (i j) // 2 # 计算中点索引 mif nums[m] target:i m 1 # 此情况说明 target 在区间 [m1, j) 中elif nums[m] target:j m # 此情况说明 target 在区间 [i, m) 中else:return m # 找到目标元素返回其索引return -1 # 未找到目标元素返回 -1Go
/* 二分查找左闭右开 */
func binarySearchLCRO(nums []int, target int) int {// 初始化左闭右开 [0, n) 即 i, j 分别指向数组首元素、尾元素1i, j : 0, len(nums)// 循环当搜索区间为空时跳出当 i j 时为空for i j {m : i (j-i)/2 // 计算中点索引 mif nums[m] target { // 此情况说明 target 在区间 [m1, j) 中i m 1} else if nums[m] target { // 此情况说明 target 在区间 [i, m) 中j m} else { // 找到目标元素返回其索引return m}}// 未找到目标元素返回 -1return -1
}在两种区间表示下二分查找算法的初始化、循环条件和缩小区间操作皆有所不同。由于“双闭区间”表示中的左右边界都被定义为闭区间因此指针 i 和 j 缩小区间操作也是对称的。这样更不容易出错因此一般建议采用“双闭区间”的写法。 优缺点
二分查找在时间和空间方面都有较好的性能。
二分查找的时间效率高。在大数据量下对数阶的时间复杂度具有显著优势。二分查找无须额外空间。相较于需要借助额外空间的搜索算法例如哈希查找二分查找更加节省空间。
然而二分查找并非适用于所有情况主要有以下原因。
二分查找仅适用于有序数据。若输入数据无序为了使用二分查找而专门进行排序得不偿失。因为排序算法的时间复杂度通常为 O(nlogn) 比线性查找和二分查找都更高。对于频繁插入元素的场景为保持数组有序性需要将元素插入到特定位置时间复杂度为 O(n) 也是非常昂贵的。二分查找仅适用于数组。二分查找需要跳跃式非连续地访问元素而在链表中执行跳跃式访问的效率较低因此不适合应用在链表或基于链表实现的数据结构。小数据量下线性查找性能更佳。在线性查找中每轮只需要 1 次判断操作而在二分查找中需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法减法共 4 ~ 6 个单元操作因此当数据量 n 较小时线性查找反而比二分查找更快。
二分查找插入点
二分查找不仅可用于搜索目标元素还具有许多变种问题比如搜索目标元素的插入位置。
无重复元素 Question 给定一个长度为 n 的有序数组 nums 和一个元素 target 数组不存在重复元素。现将 target 插入到数组 nums 中并保持其有序性。若数组中已存在元素 target 则插入到其左方。请返回插入后 target 在数组中的索引。 Python
def binary_search_insertion_simple(nums: list[int], target: int) - int:二分查找插入点无重复元素i, j 0, len(nums) - 1 # 初始化双闭区间 [0, n-1]while i j:m (i j) // 2 # 计算中点索引 mif nums[m] target:i m 1 # target 在区间 [m1, j] 中elif nums[m] target:j m - 1 # target 在区间 [i, m-1] 中else:return m # 找到 target 返回插入点 m# 未找到 target 返回插入点 ireturn iGo
/* 二分查找插入点无重复元素 */
func binarySearchInsertionSimple(nums []int, target int) int {// 初始化双闭区间 [0, n-1]i, j : 0, len(nums)-1for i j {// 计算中点索引 mm : i (j-i)/2if nums[m] target {// target 在区间 [m1, j] 中i m 1} else if nums[m] target {// target 在区间 [i, m-1] 中j m - 1} else {// 找到 target 返回插入点 mreturn m}}// 未找到 target 返回插入点 ireturn i
}有重复元素
假设数组中存在多个 target 则普通二分查找只能返回其中一个 target 的索引而无法确定该元素的左边和右边还有多少 target。
题目要求将目标元素插入到最左边所以我们需要查找数组中最左一个 target 的索引。
执行二分查找得到任意一个 target 的索引记为 k 。从索引 k 开始向左进行线性遍历当找到最左边的 target 时返回。 此方法虽然可用但其包含线性查找因此时间复杂度为 O(n) 。当数组中存在很多重复的 target 时该方法效率很低。现考虑拓展二分查找代码。整体流程保持不变每轮先计算中点索引 m 再判断 target 和 nums[m] 大小关系分为以下几种情况。
当 nums[m] target 或 nums[m] target 时说明还没有找到 target 因此采用普通二分查找的缩小区间操作从而使指针 i 和 i 向 target 靠近。当 nums[m] target 时说明小于 target 的元素在区间 [i,m−1] 中因此采用 jm−1 来缩小区间从而使指针 j 向小于 target 的元素靠近。
循环完成后i 指向最左边的 target j 指向首个小于 target 的元素因此索引 i 就是插入点。 Python
def binary_search_insertion(nums: list[int], target: int) - int:二分查找插入点存在重复元素i, j 0, len(nums) - 1 # 初始化双闭区间 [0, n-1]while i j:m (i j) // 2 # 计算中点索引 mif nums[m] target:i m 1 # target 在区间 [m1, j] 中elif nums[m] target:j m - 1 # target 在区间 [i, m-1] 中else:j m - 1 # 首个小于 target 的元素在区间 [i, m-1] 中# 返回插入点 ireturn iGo
/* 二分查找插入点存在重复元素 */
func binarySearchInsertion(nums []int, target int) int {// 初始化双闭区间 [0, n-1]i, j : 0, len(nums)-1for i j {// 计算中点索引 mm : i (j-i)/2if nums[m] target {// target 在区间 [m1, j] 中i m 1} else if nums[m] target {// target 在区间 [i, m-1] 中j m - 1} else {// 首个小于 target 的元素在区间 [i, m-1] 中j m - 1}}// 返回插入点 ireturn i
}二分查找边界
左边界 Question 给定一个长度为 n 的有序数组 nums 数组可能包含重复元素。请返回数组中最左一个元素 target 的索引。若数组中不包含该元素则返回 −1 。 回忆二分查找插入点的方法搜索完成后 i 指向最左一个 target 因此查找插入点本质上是在查找最左一个 target 的索引。考虑通过查找插入点的函数实现查找左边界。请注意数组中可能不包含 target 这种情况可能导致以下两种结果。
插入点的索引 i 越界。元素 nums[i] 与 target 不相等。
当遇到以上两种情况时直接返回 −1 即可。 为什么 i 可能会越界 考虑一个例子 假设我们有一个数组 nums [1, 2, 3, 4, 5] 并且我们的目标值 target 6。使用上述的二分查找插入点方法我们将会得到以下的过程
i0, j4, m2, nums[m]3, 3 6, 所以 im13。i3, j4, m3, nums[m]4, 4 6, 所以 im14。i4, j4, m4, nums[m]5, 5 6, 所以 im15。
现在 i 指向了索引 5这是越界的因为数组的最大索引是 4。
Python
def binary_search_left_edge(nums: list[int], target: int) - int:二分查找最左一个 target# 等价于查找 target 的插入点i binary_search_insertion(nums, target)# 未找到 target 返回 -1if i len(nums) or nums[i] ! target:return -1# 找到 target 返回索引 ireturn iGo
/* 二分查找最左一个 target */
func binarySearchLeftEdge(nums []int, target int) int {// 等价于查找 target 的插入点i : binarySearchInsertion(nums, target)// 未找到 target 返回 -1if i len(nums) || nums[i] ! target {return -1}// 找到 target 返回索引 ireturn i
}右边界
复用查找左边界
可以利用查找最左元素的函数来查找最右元素具体方法为将查找最右一个 target 转化为查找最左一个 target 1。如下图所示查找完成后指针 i 指向最左一个 target 1如果存在而 j 指向最右一个 target 因此返回 j 即可。 请注意返回的插入点是 i 因此需要将其减 1 从而获得 j 。
Python
def binary_search_right_edge(nums: list[int], target: int) - int:二分查找最右一个 target# 转化为查找最左一个 target 1i binary_search_insertion(nums, target 1)# j 指向最右一个 target i 指向首个大于 target 的元素j i - 1# 未找到 target 返回 -1if j -1 or nums[j] ! target:return -1# 找到 target 返回索引 jreturn jGo
/* 二分查找最右一个 target */
func binarySearchRightEdge(nums []int, target int) int {// 转化为查找最左一个 target 1i : binarySearchInsertion(nums, target1)// j 指向最右一个 target i 指向首个大于 target 的元素j : i - 1// 未找到 target 返回 -1if j -1 || nums[j] ! target {return -1}// 找到 target 返回索引 jreturn j
}查找不存在元素
当数组不包含 target 时最终 i 和 j 会分别指向首个大于、小于 target 的元素。可以构造一个数组中不存在的元素用于查找左右边界。
查找最左一个 target 可以转化为查找 target - 0.5 并返回指针 i 。查找最右一个 target 可以转化为查找 target 0.5 并返回指针 j 。 给定数组不包含小数这意味着我们无须关心如何处理相等的情况。因为该方法引入了小数所以需要将函数中的变量 target 改为浮点数类型。
哈希优化
在算法题中常通过将线性查找替换为哈希查找来降低算法的时间复杂度。 Question 给定一个整数数组 nums 和一个目标元素 target 请在数组中搜索“和”为 target 的两个元素并返回它们的数组索引。返回任意一个解即可。 线性查找
直接遍历所有可能的组合。开启一个两层循环在每轮中判断两个整数的和是否为 target 若是则返回它们的索引。 Python
def two_sum_brute_force(nums: list[int], target: int) - list[int]:方法一暴力枚举# 两层循环时间复杂度 O(n^2)for i in range(len(nums) - 1):for j in range(i 1, len(nums)):if nums[i] nums[j] target:return [i, j]return []Go
/* 方法一暴力枚举 */
func twoSumBruteForce(nums []int, target int) []int {size : len(nums)// 两层循环时间复杂度 O(n^2)for i : 0; i size-1; i {for j : i 1; i size; j {if nums[i]nums[j] target {return []int{i, j}}}}return nil
}此方法的时间复杂度为 O(n^2) 空间复杂度为 O(1) 在大数据量下非常耗时。
哈希查找
考虑借助一个哈希表键值对分别为数组元素和元素索引。循环遍历数组
判断数字 target - nums[i] 是否在哈希表中若是则直接返回这两个元素的索引。将键值对 nums[i] 和索引 i 添加进哈希表。 Python
def two_sum_hash_table(nums: list[int], target: int) - list[int]:方法二辅助哈希表# 辅助哈希表空间复杂度 O(n)dic {}# 单层循环时间复杂度 O(n)for i in range(len(nums)):if target - nums[i] in dic:return [dic[target - nums[i]], i]dic[nums[i]] ireturn []Go
/* 方法二辅助哈希表 */
func twoSumHashTable(nums []int, target int) []int {// 辅助哈希表空间复杂度 O(n)hashTable : map[int]int{}// 单层循环时间复杂度 O(n)for idx, val : range nums {if preIdx, ok : hashTable[target-val]; ok {return []int{preIdx, idx}}hashTable[val] idx}return nil
}此方法通过哈希查找将时间复杂度从 O(n^2) 降低至 O(n) 大幅提升运行效率。由于需要维护一个额外的哈希表因此空间复杂度为 O(n) 。尽管如此该方法的整体时空效率更为均衡因此它是本题的最优解法。
搜索算法
搜索算法用于在数据结构例如数组、链表、树或图中搜索一个或一组满足特定条件的元素。
搜索算法可根据实现思路分为以下两类。
通过遍历数据结构来定位目标元素例如数组、链表、树和图的遍历等。利用数据组织结构或数据包含的先验信息实现高效元素查找例如二分查找、哈希查找和二叉搜索树查找等。
暴力搜索
暴力搜索通过遍历数据结构的每个元素来定位目标元素。
“线性搜索”适用于数组和链表等线性数据结构。它从数据结构的一端开始逐个访问元素直到找到目标元素或到达另一端仍没有找到目标元素为止。“广度优先搜索”和“深度优先搜索”是图和树的两种遍历策略。广度优先搜索从初始节点开始逐层搜索由近及远地访问各个节点。深度优先搜索是从初始节点开始沿着一条路径走到头为止再回溯并尝试其他路径直到遍历完整个数据结构。
暴力搜索的优点是简单且通用性好无须对数据做预处理和借助额外的数据结构。
然而此类算法的时间复杂度为 O(n) 其中 n 为元素数量因此在数据量较大的情况下性能较差。
自适应搜索
自适应搜索利用数据的特有属性例如有序性来优化搜索过程从而更高效地定位目标元素。
“二分查找”利用数据的有序性实现高效查找仅适用于数组。“哈希查找”利用哈希表将搜索数据和目标数据建立为键值对映射从而实现查询操作。“树查找”在特定的树结构例如二叉搜索树中基于比较节点值来快速排除节点从而定位目标元素。
此类算法的优点是效率高时间复杂度可达到 O(logn) 甚至 O(1) 。
然而使用这些算法往往需要对数据进行预处理。例如二分查找需要预先对数组进行排序哈希查找和树查找都需要借助额外的数据结构维护这些数据结构也需要额外的时间和空间开支。
搜索算法选取
给定大小为 n 的一组数据我们可以使用线性搜索、二分查找、树查找、哈希查找等多种方法在该数据中搜索目标元素。
线性搜索二分查找树查找哈希查找查找元素O(n)O(logn)O(logn)O(1)插入元素O(1)O(n)O(logn)O(1)删除元素O(n)O(n)O(logn)O(1)额外空间O(1)O(1)O(n)O(n)数据预处理/排序 O(nlogn)建树 O(nlogn)建哈希表 O(n)数据是否有序无序有序有序无序
搜索算法的选择还取决于数据体量、搜索性能要求、数据查询与更新频率等。
线性搜索
通用性较好无须任何数据预处理操作。假如我们仅需查询一次数据那么其他三种方法的数据预处理的时间比线性搜索的时间还要更长。适用于体量较小的数据此情况下时间复杂度对效率影响较小。适用于数据更新频率较高的场景因为该方法不需要对数据进行任何额外维护。
二分查找
适用于大数据量的情况效率表现稳定最差时间复杂度为 O(logn) 。数据量不能过大因为存储数组需要连续的内存空间。不适用于高频增删数据的场景因为维护有序数组的开销较大。
哈希查找
适合对查询性能要求很高的场景平均时间复杂度为 O(1) 。不适合需要有序数据或范围查找的场景因为哈希表无法维护数据的有序性。对哈希函数和哈希冲突处理策略的依赖性较高具有较大的性能劣化风险。不适合数据量过大的情况因为哈希表需要额外空间来最大程度地减少冲突从而提供良好的查询性能。
树查找
适用于海量数据因为树节点在内存中是离散存储的。适合需要维护有序数据或范围查找的场景。在持续增删节点的过程中二叉搜索树可能产生倾斜时间复杂度劣化至 O(n) 。若使用 AVL 树或红黑树则各项操作可在 O(logn) 效率下稳定运行但维护树平衡的操作会增加额外开销。
Referenceshttps://www.hello-algo.com/chapter_searching/ 文章转载自: http://www.morning.sjpht.cn.gov.cn.sjpht.cn http://www.morning.rynq.cn.gov.cn.rynq.cn http://www.morning.njntp.cn.gov.cn.njntp.cn http://www.morning.kjmcq.cn.gov.cn.kjmcq.cn http://www.morning.ktnmg.cn.gov.cn.ktnmg.cn http://www.morning.bgkk.cn.gov.cn.bgkk.cn http://www.morning.zdhnm.cn.gov.cn.zdhnm.cn http://www.morning.qnzpg.cn.gov.cn.qnzpg.cn http://www.morning.rxlck.cn.gov.cn.rxlck.cn http://www.morning.vvbsxm.cn.gov.cn.vvbsxm.cn http://www.morning.bzlfw.cn.gov.cn.bzlfw.cn http://www.morning.bfrff.cn.gov.cn.bfrff.cn http://www.morning.smxyw.cn.gov.cn.smxyw.cn http://www.morning.ynlbj.cn.gov.cn.ynlbj.cn http://www.morning.nwwzc.cn.gov.cn.nwwzc.cn http://www.morning.ynrzf.cn.gov.cn.ynrzf.cn http://www.morning.kbynw.cn.gov.cn.kbynw.cn http://www.morning.zcwwb.cn.gov.cn.zcwwb.cn http://www.morning.jpkhn.cn.gov.cn.jpkhn.cn http://www.morning.dxqfh.cn.gov.cn.dxqfh.cn http://www.morning.rpkg.cn.gov.cn.rpkg.cn http://www.morning.qtrlh.cn.gov.cn.qtrlh.cn http://www.morning.dglszn.com.gov.cn.dglszn.com http://www.morning.btwrj.cn.gov.cn.btwrj.cn http://www.morning.qddtd.cn.gov.cn.qddtd.cn http://www.morning.zlhcw.cn.gov.cn.zlhcw.cn http://www.morning.rlksq.cn.gov.cn.rlksq.cn http://www.morning.wfqcs.cn.gov.cn.wfqcs.cn http://www.morning.tsnmt.cn.gov.cn.tsnmt.cn http://www.morning.bchhr.cn.gov.cn.bchhr.cn http://www.morning.gkfwp.cn.gov.cn.gkfwp.cn http://www.morning.gmyhq.cn.gov.cn.gmyhq.cn http://www.morning.jlnlr.cn.gov.cn.jlnlr.cn http://www.morning.qnwyf.cn.gov.cn.qnwyf.cn http://www.morning.rbzd.cn.gov.cn.rbzd.cn http://www.morning.rqgjr.cn.gov.cn.rqgjr.cn http://www.morning.wyrkp.cn.gov.cn.wyrkp.cn http://www.morning.sskhm.cn.gov.cn.sskhm.cn http://www.morning.qrwnj.cn.gov.cn.qrwnj.cn http://www.morning.qqhersx.com.gov.cn.qqhersx.com http://www.morning.yxwrr.cn.gov.cn.yxwrr.cn http://www.morning.nxfuke.com.gov.cn.nxfuke.com http://www.morning.lyhrg.cn.gov.cn.lyhrg.cn http://www.morning.lmzpk.cn.gov.cn.lmzpk.cn http://www.morning.gywxq.cn.gov.cn.gywxq.cn http://www.morning.ykbgs.cn.gov.cn.ykbgs.cn http://www.morning.rckmz.cn.gov.cn.rckmz.cn http://www.morning.tnwwl.cn.gov.cn.tnwwl.cn http://www.morning.pxdgy.cn.gov.cn.pxdgy.cn http://www.morning.ahlart.com.gov.cn.ahlart.com http://www.morning.aishuxue.com.cn.gov.cn.aishuxue.com.cn http://www.morning.mwns.cn.gov.cn.mwns.cn http://www.morning.sqmlw.cn.gov.cn.sqmlw.cn http://www.morning.mzcsp.cn.gov.cn.mzcsp.cn http://www.morning.fmkjx.cn.gov.cn.fmkjx.cn http://www.morning.kkhf.cn.gov.cn.kkhf.cn http://www.morning.nslwj.cn.gov.cn.nslwj.cn http://www.morning.hchrb.cn.gov.cn.hchrb.cn http://www.morning.yfphk.cn.gov.cn.yfphk.cn http://www.morning.nmwgd.cn.gov.cn.nmwgd.cn http://www.morning.jqrp.cn.gov.cn.jqrp.cn http://www.morning.mysmz.cn.gov.cn.mysmz.cn http://www.morning.sxhdzyw.com.gov.cn.sxhdzyw.com http://www.morning.bgygx.cn.gov.cn.bgygx.cn http://www.morning.xdqrz.cn.gov.cn.xdqrz.cn http://www.morning.rqknq.cn.gov.cn.rqknq.cn http://www.morning.bpcf.cn.gov.cn.bpcf.cn http://www.morning.nzlqt.cn.gov.cn.nzlqt.cn http://www.morning.rqkzh.cn.gov.cn.rqkzh.cn http://www.morning.redhoma.com.gov.cn.redhoma.com http://www.morning.alive-8.com.gov.cn.alive-8.com http://www.morning.rbjp.cn.gov.cn.rbjp.cn http://www.morning.xmnlc.cn.gov.cn.xmnlc.cn http://www.morning.fgtls.cn.gov.cn.fgtls.cn http://www.morning.bjjrtcsl.com.gov.cn.bjjrtcsl.com http://www.morning.wcghr.cn.gov.cn.wcghr.cn http://www.morning.nqcwz.cn.gov.cn.nqcwz.cn http://www.morning.qwmpn.cn.gov.cn.qwmpn.cn http://www.morning.rjtmg.cn.gov.cn.rjtmg.cn http://www.morning.npcxk.cn.gov.cn.npcxk.cn