二分查找

一、什么是二分查找?

二分查找针对的是一个有序的数据集合,每次通过跟区间中间的元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间缩小为0 。

比如,我们要在下图所示的有序数组中,查找值为19的数据。
target = 19;

  • low=0,high=9,mid=(low+high)/2=4;mid所在位置值大于target值,high=mid-1
  • low=0, high=mid-1=3, mid=(low+high)/2=1; mid所在位置值小于target,low=mid+1
  • low=mid+1=2, high=3, mid=2; mid所在位置值等于target,查找结束。

在这里插入图片描述

二、过程分析

  • 时间复杂度
    假设数据大小是n,每次查找后数据都会缩小为原来的一半,最坏的情况下,直到查找区间被缩小为空,才停止。所以,每次查找的数据大小变化是:
    在这里插入图片描述
    当n/(2^k)=1时。k的值就是总共比较的总次数,而每次缩小操作只涉及两个数据的大小比较,所以经过k次区间缩小操作,时间复杂度位O(k)。
    通过n/(2^k)=1,可求得k=log2n,所以时间复杂度是O(logn)。

  • 认识O(logn)
    这是一种极其高效的时间复杂度,因为logn是一个非常“恐怖“的数量级,即便n非常大,对应的logn也很小。比如n等于2的32次方,也就是42亿,而logn才32。

代码示例

普通二分查找
/*
二分查找:
1,应用环境:
   a,依赖顺序表结构
   b,有序
2,普通二分查找,循环实现
*/
func BSearch(arr []int, target int) int {
	low, high := 0, len(arr)-1
	for low <= high {  //注意点1:循环退出条件是low <= high
		mid := low + ((high - low) >> 1)  //注意点2: mid声明位置以及取值,避免溢出,位运算高效
		if arr[mid] < target {
			low = mid + 1 //注意点3:low和high更新是加减1的操作
		} else if arr[mid] > target {
			high = mid - 1
		} else {
			return mid
		}
	}
	return -1
}
/*
普通二分查找:递归实现
*/

func BSearch2(arr []int, target int) int {
	return recurBSearch(arr, 0, len(arr)-1, target)
}

func recurBSearch(arr []int, low, high, targt int) int {
	if low > high {
		return -1
	}
	mid := low + ((high - low) >> 1)
	if arr[mid] == targt {
		return mid
	} else if arr[mid] > targt {
		return recurBSearch(arr, low, mid-1, targt)
	} else {
		return recurBSearch(arr, mid+1, high, targt)
	}
}
二分查找变种
/*
二分查找变种1:查找第一个值等于目标值的元素
*/

func FindFirstTarget(arr []int, target int) int {
	low, high := 0, len(arr)-1
	for low <= high {
		mid := low + ((high - low) >> 1)
		if arr[mid] < target {
			low = mid + 1
		} else if arr[mid] > target {
			high = mid - 1
		} else { //特殊之处,当mid已经是0号位置,或者mid-1位置值不是target时,
		          //表明mid就是第一个等于target值的位置
			if mid == 0 || arr[mid-1] != target {
				return mid
			}
			high = mid - 1
		}
	}
	return -1
}
/*
二分查找变种2:查找最后一个值等于目标值的元素
*/

func FindLastTarget(arr []int, target int) int {
	arr_length := len(arr)
	low, high := 0, arr_length-1
	for low <= high {
		mid := low + ((high - low) >> 1)
		if arr[mid] < target {
			low = mid + 1
		} else if arr[mid] < target {
			high = mid - 1
		} else { //特殊之处
			if mid == arr_length-1 || arr[mid+1] != target {
				return mid
			}
			low = mid + 1
		}
	}
	return -1
}
/*
二分查找变种3:查找第一个大于等于目标值的元素
*/

func FindFirstGETarget(arr []int, target int) int {
	low, high := 0, len(arr)-1
	for low <= high {
		mid := low + ((high - low) >> 1)
		if arr[mid] < target {
			low = mid + 1
		} else { //特殊之处
			if mid == 0 || arr[mid-1] < target {
				return mid
			}
			high = mid - 1
		}
	}
	return -1
}
/*
二分查找变种4:查找最后一个小于等于目标值的元素
*/

func FindLastLETarget(arr []int, target int) int {
	arr_length := len(arr)
	low, high := 0, arr_length-1
	for low <= high {
		mid := low + ((high - low) >> 1)
		if arr[mid] <= target { //特殊之处
			if mid == arr_length-1 || arr[mid+1] > target {
				return mid
			}
			low = mid + 1
		} else {
			high = mid - 1
		}
	}
	return -1
}
示例
/*
二分查找应用:计算数字x的平方根,精度要求为y
*/

func ComputeSqureRoot(x, y float64) float64 {
	if x < 0 {
		return -1
	}
	if x == 1 || x == 0 {
		return x
	}
	var low, high float64
	high = x
	if x < 1 {
		low = 0
	} else {
		low = 1
	}
	for low <= high {
		mid := low + (high-low)/2
		temp := mid * mid
		if temp+y >= x && temp-y <= x {
			return mid
		} else if temp < x {
			low = mid
		} else {
			high = mid
		}
	}
	return -1
}

应用前提

  • 存储结构依赖顺序表结构,即数组
  • 针对的是有序数据

应用建议

  • 二分查找适用于一次排序多次查找的场景中。
  • 二分查找适用于单次比较非常耗时,需尽量减少比较次数的场景。
  • 二分查找不适用于数据量太大的场景,因为二分查找需要连续内存。

应用实例

  • 快速定位ip地址的归属地?
    在这里插入图片描述
    a:先把ip地址转换为32位整数
    b:对这12万个ip地址升序排序
    c:在有序ip数组中,查找最后一个小于等于目标ip的数值
  • leetcode编程题:搜索旋转排序数组
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值