二分查找
核心思想:二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0。
写二分查找的三个条件
- 循环退出条件 (注意是 low<=high,而不是 low)
- mid 的取值
实际上,mid=(low+high)/2 这种写法是有问题的。因为如果 low 和 high 比较大的话,两者之和就有可能会溢出。改进的方法是将 mid 的计算方式写成 low+(high-low)/2。更进一步,如果要将性能优化到极致的话,我们可以将这里的除以 2 操作转化成位运算 low+((high-low)>>1)。因为相比除法运算来说,计算机处理位运算要快得多。
- low 和 high 的更新【low=mid+1,high=mid-1。注意这里的 +1 和 -1,如果直接写成 low=mid 或者 high=mid,就可能会发生死循环。比如,当 high=3,low=3 时,如果 a[3]不等于 value,就会导致一直循环不退出。) 一般情况代码实现】
迭代:
func find(a []int, value int) int {
aLen := len(a)
low := 0
high := aLen - 1
for low <= high {
mid := low + (high - low) / 2
if a[mid] == value {
return mid
} else if a[mid] > value {
high = mid - 1
} else {
low = mid + 1
}
}
return -1
}
递归:
func recursionFind(a []int, low, high, value int) int {
if low > high {
return -1
}
mid := low + (high - low) / 2
if a[mid] == value {
return mid
} else if a[mid] > value {
return recursionFind(a, low, mid-1, value )
} else {
return recursionFind(a, mid+1, high, value)
}
}
变体一:查找第一个值等于给定值的元素
代码:
func binarySearch(a []int, value int) int {
aLen := len(a)
low := 0
high := aLen-1
for low <= high {
mid := low + (high - low) / 2
if a[mid] < value {
low = mid + 1
} else if a[mid] > value {
high = mid - 1
} else { // value == mid
//如果mid等于0,那么这个元素是数组的第一个元素,肯定是我们要找的
// 如果 mid 不等于 0,但 a[mid]的前一个元素 a[mid-1]不等于 value,那也说明 a[mid]就是我们要找的第一个值等于给定值的元素
if mid == 0 || a[mid-1] != value {
return mid
} else {
//如果经过检查之后,a[mid-1]也等于value, 说明此元素必然存在【low, mid-1】区间内
high = mid - 1
}
}
}
return -1
}
变体二:查找最后一个值等于给定值的元素
代码:
func binarySearch(a []int, value int) int {
aLen := len(a)
low := 0
high := aLen-1
for low <= high {
mid := low + (high - low) / 2
if a[mid] < value {
low = mid + 1
} else if a[mid] > value {
high = mid - 1
} else {
if mid == aLen-1 || a[mid+1] != value {
return mid
} else {
low = mid + 1
}
}
}
return -1
}
变体三:查找第一个大于等于给定值的元素
代码:
func binarySearch(a []int, value int) int {
aLen := len(a)
low := 0
high := aLen-1
for low <= high {
mid := low + (high - low) / 2
if a[mid] >= value {
//mid ==0说明是第一个元素, 返回
//a[mid-1] < value 说明 mid 是第一个大于或等于的元素, 返回
if mid ==0 || a[mid-1] < value {
return mid
} else {
//否则,查找区间在【low,mid-1】
high = mid - 1
}
} else {
low = mid + 1
}
}
return -1
}
变体四:查找最后一个小于等于给定值的元素
代码:
func binarySearch(a []int, value int) int {
aLen := len(a)
low := 0
high := aLen-1
for low <= high {
mid := low + (high - low) / 2
if a[mid] <= value {
//如果mid是最后一个元素,则直接返回
//如果a[mid+1] > value 说明mid 是最后一个大于等于value的元素
if mid == aLen-1 || a[mid+1] > value {
return mid
} else {
//否则查找区间在【mid+1,high】之间
low = mid + 1
}
} else {
high = mid - 1
}
}
return -1
}