算法与数据结构--简单易懂的二分查找法

查找问题是计算机中非常重要的基础问题,但是查找往往建立在查找方法上,通常都是排序的数据

二分查找法

对于有序数列,才能使用二分查找法,所以查找前就要用到排序算法。二分查找法的时间复杂度是O(logN),对于顺序数组,二分查找法已经是特别快了,所以顺序数组的查找操作,时间复杂度是O(logN)

在有序数组中(假设为升序),先找到中间位置,然后比较要查找的数是比这个大还是小,比中间小就在左边找,大就往右边找

在这里插入图片描述
注意,写代码时,要清楚的定义范围,到底是前闭后闭,还是前闭后开,还是其他。

public static int binarySearch(int[] arr, int target) {
	 int l = 0;
	 int r = arr.length - 1;
	 //在arr[l,r]之间查找target位置,定义好好查找的范围区间,是开还是闭,再写对应代码
	 while(l <= r) {
		 int mid = (l+r) / 2;
		 if(target < arr[mid]) {
			 r = mid -1;
		 } else if(target > arr[mid]) {
			 l = mid +1;
		 } else {
			 return mid;
		}
	 }
	 return -1;
}

上述代码就是最早期的二分查找法,但是还是有bug的,那就是 int mid = (l+r) / 2; 如果l和r都是int中的较大值,那么相加就会超出int的最大范围,所以定义中间数时,最好用减法,控制在int范围以内:

int mid = l + (l-r) / 2;//用减法,整个区间值除以2

,除了循环语句,我们还可以使用递归的形式实现代码:

//使用递归实现二分查找法,思维要容易一些,但是通常递归在性能上回略差
//因为每次递归会多执行一些判断条件或者赋值操作
//在arr[l,r]之间查找target位置,定义好好查找的范围区间,是开还是闭,逻辑理清楚了再写对应代码
public static int binarySearch2(int[] arr, int left, int right , int target) {
	if(left > right || left < 0){
		return -1;
	}
	int mid = left + (right - left) / 2;
	if(target == arr[mid]) {
		return mid;
	}
	if(target > arr[mid]) {
		left = mid +1;
		return binarySearch2(arr, left, right, target);
	} else {
		right = mid - 1;
		return binarySearch2(arr, left, right, target);
	}
}

二分查找法的扩展: floor和ceil函数

上面假设二分查找的数据是没有重复的,假设数据有重复的值,如果按照上述方法查找,则最后找到位置是不稳定的,有可能是这一串重复数字的第一个或者最后一个或者中间的值。此时,就需要定义floor和ceil两个方法。

floor: 很常见的一个方法,通常是去比目标值小的最大值,或者向下取整

ceil: 与floor相反,是查找比目标值大的最小值,或者向上取整。

这里,我们定义的floor和ceil函数分别是目标值的最小位置和最大位置,如果数据中没有目标值,则是比目标值小的最大值的位置,以及比目标值大的最小值的位置。实现如下:

floor函数

public static int floor(int[] arr, int left, int right , int target) {
	return floor(arr, left, right, target, -1); //初始值传入 -1, 在这里写死,所以下面的方法不对外开放,防止出错
}
//递归实现
//查找目标值的最小位置,如果目标值不存在,则返回小于目标值的最大数的位置, floor为当前找到的最合适的位置,初始传入为-1
private static int floor(int[] arr, int left, int right , int target, int floor) {
	if(left > right) {
		return floor == -1 ? -1 : floor; //如果找遍了整个数组也没找到,则返回小于目标值的最大数的位置
	}
	int mid = left + (right - left) / 2;
	if(arr[mid] == target) {
		right = mid -1;
		floor = mid;//把当前找的值的位置赋值给floor后,继续往左边找,查看是否还有值
		return floor(arr, left, right, target, floor);//继续往左边找
	}
	if(target < arr[mid]) {
		right = mid - 1; //减一以后继续往左边找,查看是否还有值
		return floor(arr, left, right, target, floor);//继续往左边找
	} else {
		left = mid + 1;
		return floor(arr, left, right, target, floor);//继续往右边找
	}
}

ceil函数:

public static int ceil(int[] arr, int left, int right , int target) {
	return ceil(arr, left, right, target, -1); //初始值传入 -1, 在这里写死,所以下面的方法不对外开放,防止出错
}
//递归实现
//查找目标值的最大位置,如果目标值不存在,则返回大于目标值的最小位置
public static int ceil(int[] arr, int left, int right , int target, int ceil) {
	if(left > right) {
		return ceil == -1 ? -1 : ceil;
	}
	
	
	int mid = left + (right - left) / 2;
	if(target == arr[mid]) {
		left = mid + 1;// +1 以后继续往右边找,查看是否还有值
		return ceil(arr, left, right, target, ceil);
	}
	
	if(target < arr[mid]) { //则继续往左边找
		right = mid -1;
		return ceil(arr, left, right, target, ceil);//继续往左边找
	} else {
		left = mid + 1;
		return ceil(arr, left, right, target, ceil);//继续往右边找
	}
}

后面将在二分查找法的基础上去介绍二叉树和红黑树两种数据结构。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值