算法介绍
二分查找也称为折半查找,是一种用于在有序数据结构(通常是有序数组)中查找特定元素的高效算法。该算法的思想基于分治策略,通过将数据结构分成两半,然后比较目标元素与中间元素的大小关系,从而确定在哪一半继续查找。
算法问题描述
给定一个有序数组(通常是升序排列)以及一个目标元素,目标是确定该元素是否在数组中,如果存在则返回其索引,否则返回 -1。
算法思想
二分查找算法采用分治策略,将数组分成两半,然后通过比较目标元素与中间元素的大小,确定继续查找的方向。如果目标元素等于中间元素,则找到了,返回索引。如果目标元素小于中间元素,则在左半部分查找。如果目标元素大于中间元素,则在右半部分查找。这个过程不断重复,直到找到目标元素或确定它不在数组中。
算法步骤
- 分(Divide):将给定有序数组分为两半,找到数组的中间元素。
- 治(Conquer):比较中间元素与目标元素的大小。
- 如果中间元素等于目标元素,返回中间元素的索引,查找结束。
- 如果中间元素大于目标元素,说明目标元素可能在左半部分,继续在左半部分递归查找。
- 如果中间元素小于目标元素,说明目标元素可能在右半部分,继续在右半部分递归查找。
- 合(Merge):重复上述过程,直到找到目标元素或确定它不在数组中。如果左边界大于右边界,表示目标元素不在数组中,返回 -1。
算法图示
算法伪代码
递归版本
function binarySearchRecursive(array, target, left, right)
if left > right
return -1 # 未找到目标元素
mid = (left + right) / 2 # 计算中间索引
if array[mid] == target # 如果中间元素等于目标元素
return mid # 返回中间索引,目标元素已找到
else if array[mid] < target # 如果中间元素小于目标元素
return binarySearchRecursive(array, target, mid + 1, right) # 在右半部分递归查找
else # 否则,中间元素大于目标元素
return binarySearchRecursive(array, target, left, mid - 1) # 在左半部分递归查找
# 调用二分查找函数
result = binarySearchRecursive(sortedArray, targetElement, 0, length(sortedArray) - 1)
非递归版本
function binarySearch(array, target)
left = 0 # 初始化左边界为数组的起始索引
right = length(array) - 1 # 初始化右边界为数组的结束索引
while left <= right # 当左边界小于等于右边界时循环
mid = (left + right) / 2 # 计算中间索引
if array[mid] == target # 如果中间元素等于目标元素
return mid # 返回中间索引,目标元素已找到
else if array[mid] < target # 如果中间元素小于目标元素
left = mid + 1 # 更新左边界为 mid + 1,在右半部分查找
else # 否则,中间元素大于目标元素
right = mid - 1 # 更新右边界为 mid - 1,在左半部分查找
return -1 # 循环结束,未找到目标元素,返回 -1 表示不存在
# 调用二分查找函数
result = binarySearch(sortedArray, targetElement)
算法性能
时间复杂度
- 递归版本的二分查找在每次递归中将搜索范围缩小一半,因此具有 O(log n) 的时间复杂度,其中 n 是数组的大小。这使得它成为一种高效的查找算法。
- 非递归版本的二分查找同样具有 O(log n) 的时间复杂度,其中 n 是数组的大小。它以相同的效率快速查找目标元素。
- 综上,具有 O(logn) 的时间复杂度。
空间复杂度
- 递归版本的二分查找算法使用函数调用栈来存储递归调用的上下文。在最坏情况下,递归调用会达到 O(log n) 的深度,因此空间复杂度也为 O(log n)。这意味着在非常大的数据集上,可能会占用相对较多的内存。
- 与递归版本相比,非递归版本的空间复杂度更低。它不需要函数调用栈来存储递归上下文,因此空间复杂度是 O(1),即常数级别的内存开销。
- 综上,非递归的空间开销更小,空间复杂度是 O(1)。
稳定性
二分查找是稳定的,即在相同数组中查找相同元素时,始终返回相同的结果。
适用性
- 递归版本的二分查找适用于有序数组中查找元素的场景。由于它需要递归调用,对于非递归版本可能会有稍微更高的内存开销,因此在特定内存受限或追求最大性能的情况下,迭代版本可能更受欢迎。
- 非递归版本的二分查找通常更受欢迎,因为它在大多数编程语言中不会导致栈溢出的问题。此外,它的空间开销更小,因此适用于内存受限的环境和对性能要求较高的应用。
算法总结
总的来说,递归版本的二分查找是一种高效、简洁的查找算法,特别适用于有序数组的情况。但在某些编程语言中,递归调用可能会导致栈溢出,因此在这种情况下可能需要谨慎使用或选择迭代版本。非递归版本的二分查找是一种高效、稳定且经常使用的查找算法。在绝大多数情况下,它是首选的二分查找实现。