目录
二分查找
原理:每次将查找范围缩小一半
二分查找(Binary Search)是一种高效的搜索算法,用于在有序数组中查找特定元素的位置。
其基本原理是将查找范围分成两半,然后判断目标元素与中间元素的大小关系,从而确定目标元素可能存在的区间,然后重复这个过程,缩小查找范围,直到找到目标元素或确定目标元素不存在。
前提:有序数组
二分查找的前提是数组必须是有序的,如果数组无序,需要先进行排序操作。
复杂度:时间复杂度 O ( l o g n ) O(log n) O(logn)
二分查找的关键在于每次将查找范围缩小一半,因此其时间复杂度为 O(log n),其中 n 是数组的大小。
二分查找的Go代码实现
源码地址: GitHub-golang版本(有对应的单元测试代码)
思路分析
- 首先,确定要查找的范围。将查找范围的起始索引(通常记为 left)和结束索引(通常记为 right)初始化为数组的两个端点。
- 计算中间索引 mid,即
mid = (left + right) / 2
。这将把查找范围分成两半。- 优化1:由于在计算机中,除法的效率非常低,所以这里用位运算代替除法:
mid = (left + right) >> 1
- 优化2:假如left和hign很大的话,
left+right
可能会溢出,所以可以改成:mid = left + ((right - left) >> 1)
- 优化1:由于在计算机中,除法的效率非常低,所以这里用位运算代替除法:
- 比较中间元素 arr[mid] 与目标元素 target 的大小关系:
- 如果 arr[mid] == target,则找到了目标元素,返回 mid。
- 如果 arr[mid] < target,则目标元素可能在右半部分,将 left 更新为 mid + 1。
- 如果 arr[mid] > target,则目标元素可能在左半部分,将 right 更新为 mid - 1。
- 重复步骤 2 和 3,不断缩小查找范围,直到 left 大于 right,表示查找范围为空,此时目标元素不存在,返回 -1。
循环的方式
func SearchByFor(nums []int, target int) int {
ret := -1
length := len(nums)
if length == 0 {
return ret
}
left, right := 0, length-1
for left <= right {
mid := left + ((right - left) >> 1)
if nums[mid] == target {
return mid
} else if nums[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return ret
}
递归的方式
func SearchByRecursion(nums []int, target int) int {
length := len(nums)
if length == 0 {
return -1
}
return findTarget(nums, 0, length-1, target)
}
func findTarget(arr []int, left int, right int, target int) int {
ret := -1
if left > right {
return ret
}
mid := left + ((right - left) >> 1)
if arr[mid] == target {
return mid
} else if arr[mid] < target {
return findTarget(arr, mid+1, right, target)
} else {
return findTarget(arr, left, mid-1, target)
}
}
进阶考虑:元素中有重复的二分查找
找到后再线性找
func SearchByForLeftInRepeatArr(nums []int, target int) int {
ret := -1
length := len(nums)
if length == 0 {
return ret
}
left, right := 0, length-1
for left <= right {
mid := left + ((right - left) >> 1)
if nums[mid] == target {
for mid > 0 && nums[mid] == target {
mid--
if mid == 0 && nums[mid] == target {
return mid
}
}
return mid + 1
} else if nums[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return ret
}
找到后再二分查找
下面代码在找到目标值时会继续向左搜索,直到找到第一个不等于目标值的元素。通过这种方式,可以有效地找到目标值的最左位置,时间复杂度仍然是 O(log n)。
func Search2ByForLeftInRepeatArr(nums []int, target int) int {
ret := -1
length := len(nums)
if length == 0 {
return ret
}
left, right := 0, length-1
for left <= right {
mid := left + ((right - left) >> 1)
if nums[mid] == target {
right = mid - 1
} else if nums[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
if left < length && nums[left] == target {
return left
}
return ret
}