核心思想:
将有序序列二等分,如果目标数比中值小,继续往左边递归二分,再取中值,如果比中值大则往右边递归,一直到中值索引刚好落在目标值上(找到)或者一直二等分到一个元素无法再分割结束(未找到)。
二分递归实现
public static int binarySearch(int[] arr, int target, int left, int right) {
if (left > right) {
return -1;
}
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] > target) {
return binarySearch(arr, target, left, mid - 1);
} else {
return binarySearch(arr, target, mid + 1, right);
}
}
二分非递归实现
public static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
二分搜索变体 差值搜索
- mid = left + (right - left) * (targetValue - arr[left]) / (arr[right] - list[left])
/**
* 差值查找 是对二分查找的优化 核心公式在于求出 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;
}
}
斐波那契黄金比例查找
public class FibonacciSearch {
public static void main(String[] args) {
int[] arr = {1, 8, 10, 89, 1000, 1234};
int index = fibonacciSearch(arr, 1234);
System.out.println("fibonacci index => " + index);
}
/**
* 创建一个斐波那契数列 非递归实现
*
* @param arr
* @return
*/
public static int[] createFibonacci(int[] arr) {
if (arr.length == 1) {
arr[0] = 1;
return arr;
} else if (arr.length == 2) {
arr[0] = 1;
arr[1] = 1;
return arr;
}
arr[0] = 1;
arr[1] = 1;
for (int i = 2; i <= arr.length - 1; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr;
}
public static int fibonacciSearch(int[] arr, int target) {
// 创建一个斐波那契数列
int[] fibonacci = createFibonacci(new int[20]);
// 低位索引
int low = 0;
// 高位索引
int high = arr.length - 1;
// 初始化斐波那契数列索引
int k = 0;
// 数组长度
int n = arr.length;
// 根据数组长度找到最接近斐波那契数列的k值 要找到根据数组长度来切割的点
while (n > fibonacci[k]) {
k++;
}
// 为满足斐波那契黄金分割数量要求 如果数组长度不足得需要将数组最后一个元素补齐一直到满足斐波那契分割的长度要求
// 如这里是列子数字长度是6 但最接近6的斐波那契数是8 所以需要补2位 补成{1,8, 10, 89, 1000, 1234,1234,1234} 这样才能使得斐波那契能分割
// 这里使用了Arrays.copyOf方法拷贝了一个新的数组并将长度补齐
int[] tempArr = Arrays.copyOf(arr, fibonacci[k]);
for (int i = arr.length; i < tempArr.length; i++) {
tempArr[i] = arr[arr.length - 1];
}
// 低位索引 低于 个高位索引 就一直循环
while (low <= high) {
// 找到斐波那契的黄金分割点 mid 第一次循环进入 low = 0 k=5
// mid = 0 + f[5-1] -1; mid = 0+f[4]-1 => 0 + 5 -1 = 4 相当于数组被分割成了0-4索引 一共有5个元素 后面8-5=3个元素 被分割成了一个斐波那契数列
// 第一次分割0-4 mid = 0 + f[5-1-1] - 1 ==> mid = 0 + 2 -1 => mid=1 数组被分成了0-1 和1-4 0-1有2个元素 1-4有3个元素 又被分成了一个斐波那契数列
// 最后-1是因为java数组从0开始
int mid = low + fibonacci[k - 1] - 1;
if (target < tempArr[mid]) {
high = mid - 1;
// 这里 k-- 是为了得到斐波那契数列k之前那的那个数从而得到长度进一步切分 这里k的算法与mid的求值方法有关
// 上面的mid中值算法把 f(k-1)放在了左边 f(k-2)放在了右边 所以移动的时候左边是k-1 右边是k-2
// 如 8 被切分成了 5-3 如果往左找 就是找前面5个元素 5个元整正好是k[4]
k--;
} else if (target > tempArr[mid]) {
low = mid + 1;
// 与往左找相反 如果往右找 的是k-2 因为斐波那契 f(k) = f(k-1) + f(k-2) 往左是f(k-1)右边则是f(k-2)
// 同样以第一次分切 8 被切分成了 5-3 右边是3 正好是f(3) f(5)全部 = f(4)左边 + f(3)右边
k -= 2;
} else {
// 这里判断mid 与arr.length-1 是因为 有可能我们数组是经过补足成斐波那契长度的数组 后面全部以最后一个元素填充
// 如果mid 落在了后面补足元素的索引上不能直接返回mid 因为mid已经超过了原来数组的索引
// 又因为 超过原数组的都是由最后一个元素补足的则直接返回最后一个元素索引也是一样的
if (mid <= arr.length - 1) {
return mid;
} else {
return arr.length - 1;
}
}
}
return -1;
}
}