二分查找
核心思想:将有序序列二等分,如果目标数比中值小,继续往左边递归二分,再取中值,如果比中值大则往右边递归,一直到中值索引刚好落在目标值上(找到)或者一直二等分到一个元素无法再分割结束(未找到)。
代码示例
这里列出了两个方法一个是返回一个是找到元素索引,一个是返回所有找到元素索引的集合。返回集合的思路也很简单,只需要在找到目标元素之后分别向左和向右循环找值相等的元素集合,需要注意的是在循环的之后要保证索引不越界即可。
/**
* 查找多 个数据的二分查找法
*
* @param arr 被查找的数组
* @param left 左边界
* @param right 右边界
* @param target 需要查找的数据
* @return 返回一个找到元素的list集合
*/
public static List<Integer> multipleBinarySearch(int[] arr, int left, int right, int target) {
System.out.println("binarySearch调用1次");
ArrayList<Integer> indexes = new ArrayList<>();
if (left <= right) {
int mid = (left + right) / 2;
int midVal = arr[mid];
if (target > midVal) {
// 向右递归
return multipleBinarySearch(arr, mid + 1, right, target);
} else if (target < midVal) {
// 向左递归
return multipleBinarySearch(arr, left, mid - 1, target);
} else {
// 找到了 index 然后分别向左和向右查找相同的数
indexes.add(mid);
// 向左查找
int tempIndex = mid - 1;
while (arr[tempIndex] == midVal && tempIndex > 0) {
indexes.add(tempIndex--);
}
// 向右查找 重置临时指针
tempIndex = mid + 1;
while (arr[tempIndex] == midVal && tempIndex < arr.length - 1) {
indexes.add(tempIndex++);
}
}
}
return indexes;
}
/**
* 返回第一个找到值的索引
*
* @param arr 被查找的数组
* @param left 左边界
* @param right 右边界
* @param target 需要查找的数据
* @return
*/
public static int binarySearch(int[] arr, int left, int right, int target) {
System.out.println("binarySearch调用1次");
if (left <= right) {
int mid = (left + right) / 2;
int midVal = arr[mid];
if (target > midVal) {
// 向右递归
return binarySearch(arr, mid + 1, right, target);
} else if (target < midVal) {
// 向左递归
return binarySearch(arr, left, mid - 1, target);
} else {
// 找到了
return mid;
}
}
return -1;
}
插值查找
核心思想: 插值查找十分适合用于查找有序序列间隔差不多的序列,如间隔相同的等差序列,使用核心公式mid = left + (right-left) * (target - arr[left]) / (arr[right] - arr[left]) 计算出中值索引之后可有效的减少递归的次数,但在差值间隔较大的有序序列中递归的次数不一定比普通的二分查找少,其他的实现跟二分查找一样,只是在判断递归结束条件的使用,因为中值索引的计算引入了目标数据target,为防止target传入过大引起mid计算出来的结果超过原数组长度,需要增加额外的判断。
代码示例
/**
* 差值查找 是对二分查找的优化 核心公式在于求出 mid求值优化
* 要求被查找数据是有序序列
* 在差值相近的序列中性能提升明显,在跨度较大的有序序列中不一定比普通的二分查找更快
* mid = left + (right - left) * (targetValue - arr[left]) / (arr[right] - list[left]);
*
* @param arr 被查找的数组
* @param left 左边界
* @param right 右边界
* @param target 需要查找的数据
* @return
*/
public static int interpolationSearch(int[] arr, int left, int right, int target) {
System.out.println("interpolationSearch调用1次");
// 必须添加该条件不然可能会越界 target < arr[0] || target > arr[arr.length - 1]
// 因为target参与了mid的运算如果 target是一个很大的数 如1000000 此时计算出来的mid的值远远大于arr.length-1
if (left > right || target < arr[0] || target > arr[arr.length - 1]) {
return -1;
}
int mid = left + (right - left) * (target - arr[left]) / (arr[right] - arr[left]);
int midVal = arr[mid];
if (target < midVal) {
// 左
return interpolationSearch(arr, left, mid - 1, target);
} else if (target > midVal) {
return interpolationSearch(arr, mid + 1, right, target);
} else {
return mid;
}
}