时间复杂度
O(logn)
代码实现
简单(序列中不存在重复)二分查找的实现
package main
import "fmt"
// 递归实现
func BinarySearchRecursive(arr []int, item int) int {
n := len(arr)
if n < 1 {
return -1
}
return BinarySearchCore(arr, 0, n-1, item)
}
func BinarySearchCore(arr []int, start, end, item int) int {
if start > end {
return -1
}
mid := (start+end) / 2
if item < arr[mid] {
end = mid-1
} else if item > arr[mid] {
start = mid+1
} else {
return mid
}
return BinarySearchCore(arr, start, end, item)
}
// 非递归实现
func BinarySearch(arr []int, item int) int {
n := len(arr)
if n < 1 {
return -1
}
start := 0
end := n-1
for start <= end {
mid := (start+end) /2
if item < arr[mid] {
end = mid-1
} else if item > arr[mid] {
start = mid+1
} else {
return mid
}
}
return -1
}
func main() {
arr := []int{0,1,2,3,4,5,6,7,8,9}
m := BinarySearchRecursive(arr, 3)
fmt.Println(m)
i := BinarySearch(arr, 3)
fmt.Println(i)
}
代码剖析
- for循环条件是 start <= end ,非 start < end 。
- mid取值mid := (start+end) / 2,当start和end很大的情况下,相加有可能会发生溢出情况,所以优化为:mid:=start+(end-start)/2 或者更进一步,转化为位运算:mid:=start+((end-start)>>2)。
- start和end的更新,注意为mid-1或者mid+1。
场景局限性
- 二分查找依赖的是顺序表结构,简单说就是数组。链表不可以,因为链表不支持按下标随机访问。非顺序表结构的数据存储,都比不可以。
- 必须是有序的。而且静态数据比较好,频繁的插入,删除,更新的序列,需要频繁维护有序性的序列,不适用。比较适用,一次比较维护有序,多次二分查找的场景。对于频繁更新的序列,可以用堆排序。
- 数据量不要太小,数据量太小的话,直接用顺序查找,代替二分,更简单快捷。
- 数据量不要太大,因为是顺序表存储结构,数据的存储,需要连续的内存,因为对内存要求“连续”比较苛刻,比如1GB数据,需要1GB连续内存,及时2GB的内存大小,但都是零散内存的,没有1GB的连续内存,用数组存储也无法申请,无法执行。
优点
二分查找除了本身数据占用内存外,计算比较需要的内存开销是很小的,相比二叉树,散列表等,要小的多。
二分查找是最省内存的查找方式。