查找问题是计算机中非常重要的基础问题,但是查找往往建立在查找方法上,通常都是排序的数据
二分查找法
对于有序数列,才能使用二分查找法,所以查找前就要用到排序算法。二分查找法的时间复杂度是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);//继续往右边找
}
}
后面将在二分查找法的基础上去介绍二叉树和红黑树两种数据结构。