算法篇——二分查找
2021年12月29日 · 164 字 · 1 分钟
本系列文章将学习/复习常用算法和数据结构。希望能够深入浅出的将复杂的知识讲清楚、说明白。
本文介绍第一个基础算法:二分查找。二分查找算法可以在有序
的数组中快速查询指定值。
- 要求:有序数组
- 时间复杂度:O(logN)
- 空间复杂度:O(1)
例子
大家应该玩过数字猜大小的游戏,接下来看看实际过程。
主持人从1~10选择1个数字,参与者每次猜1个数字,主持人给出提示正确/大了/小了。次数最少的获胜。
线性查找
- 主持人选定数字5。
- 参与者:10
- 主持人:大了
- 参与者:9
- 主持人:大了
- 参与者:8
- 主持人:大了
- …
- 参与者:5
- 主持人:正确
参与者采用线性报数方式,从最大的数字开始报,每次减1直到猜中。上面的例子主持人选的数字是5,参与者猜了6次(10,9,8,7,6,5)。效率是O(N)。
主持人的大了/小了
提示没有利用上,这肯定不是效率最高的方法。
二分查找
- 主持人选定数字8。
- 参与者:5
- 主持人:小了
- 参与者:7
- 主持人:小了
- 参与者:8
- 主持人:正确
参与者根据主持人的大了/小了
提示每次调整猜测范围直到猜中。上面的例子中主持人选的数字是8,参与者猜了3次:
- (0+10)/2 => 5,小了,所以下一次应该猜 比5大的数字,从 5 ~ 10继续猜
- (5+10)/2 => 7(8也可以),小了,所以下一次继续猜比7大的数字,从7~10继续猜
- (7+10)/2 => 8(9也可以),正确
可以看到二分查找法每次都能过滤掉1半的数据,达到了O(logN)的时间复杂度
代码示例
给定一个有序数组,返回指定值的索引,如果有序数组不存在该值,返回-1。
思路:
- 找中间值,(0 + 数组最后一位的所以)/2得到中间值的位置,然后对比中间值和目标值大小
- 如果目标值比中间值小,那么目标值在数组前半部分,应该继续查找 0 ~ 数组中间索引-1的这部分
- 如果目标值比中间值大,那么目标值在数组后半部分,应该继续查找 数组中间索引+1 ~ 数组结尾的这部分
为什么继续查找时中间索引要+1或者-1移动?
因为中间值已经比对过了,不满足条件,所以可以直接跳过中间值往前面后者后面一个位置继续查找
package main
import "fmt"
func main() {
fmt.Println(search([]int{1, 2, 3, 4, 5, 6, 7}, 5))
}
func search(nums []int, target int) int {
begin := 0
end := len(nums) - 1
for begin <= end { // 开始位置不能超过结束位置,超过证明所以数据都查过了
midIndex := (begin + end) / 2 // 中间索引
midValue := nums[midIndex] // 中间值
fmt.Printf("check pos(%v) value(%v)\n", midIndex, midValue)
if target < midValue { // 目标值比中间值小,所以在左边,将end移动到中间索引-1
end = midIndex - 1
continue
}
if target > midValue { // 右半边,begin移动到中间索引+1
begin = midIndex + 1
continue
}
// 相等
return midIndex
}
return -1
}
输出如下
check pos(3) value(4)
check pos(5) value(6)
check pos(4) value(5)
4