查找算法
1. 线性查找
public static int seqSearch(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target) {
return i;
}
}
return -1;
}
2. 二分查找
- 二分查找通常用于查找单调数组中的某一值,但是扩展的二分查找可以查找单调函数的某一值(将循环体内的nums[]换成function()即可)
- 核心:搜索区间都为闭,while里面带等号,mid必须加减1,while结束返回-1;
2.1 迭代二分查找
/**
* 迭代二分查找(最常用)
*
* @param nums 输入数组
* @param target 要查找的值
* @return 找到查找值返回对应索引,没有则返回-1
*/
public static int iterativeBinarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid = 0;
if (target < nums[left] || target > nums[right]) {
return -1;
}
while (left <= right) {
mid = left + ((right - left) >> 1);
if (nums[mid] == target) {
return mid;
}else if (nums[mid] > target) {
right = mid - 1;
}else if (nums[mid] < target) {
left = mid + 1;
}
}
//没找到返回-1
return -1;
}
2.2 递归二分查找
/**
* 递归二分查找
*
* @param nums 输入数组
* @param target 要查找的值
* @param left 数组左边的索引
* @param right 数组右边的索引
* @return 找到查找值返回对应索引,没有则返回-1
*/
public static int recursionBinarySearch(int[] nums,int target,int left,int right){
if(target < nums[left] || target > nums[right] || left > right){
return -1;
}
int mid = left + ((right - left) >> 1);
if(nums[mid] > target){
//比关键字大则关键字在左区域
return recursionBinarySearch(nums, target, left, mid - 1);
}else if(nums[mid] < target){
//比关键字小则关键字在右区域
return recursionBinarySearch(nums, target, mid + 1, right);
}else {
return mid;
}
}
2.3 左侧二分查找
- 核心:搜索区间左闭右开,while用小于号,if相等相反边界(左侧搜索改right,右侧搜索改left),加1放在左边界,右边界直接等mid,while结束返回左边界,右侧搜索返左边界减1!
/**
* 找到目标值得左边界
* 对于 nums = [2,3,5,7], target = 1,返回结果 0。含义是:nums 中小于 target 的元素有 0 个。
* 对于 nums = [2,3,5,7], target = 8,返回结果 4。含义是:nums 中小于 8 的元素有 4 个。
*
* @param nums 输入数组
* @param target 要查找的值
* @return 找到目标值的左边界
*/
public static int leftBinarySearch(int[] nums,int target){
int left= 0;
int right = nums.length; // 与基础二分查找的区别!
int mid = 0;
while(left < right){ // 与基础二分查找的区别!
mid = left + ((right - left) >> 1);
if(nums[mid] == target){
right = mid; // 找到 target 时不要立即返回,而是缩小「搜索区间」的上界 right,在区间 [left, mid) 中继续搜索,
}else if(nums[mid] > target){
right = mid; // 与基础二分查找的区别!「搜索区间」是 [left, right)
}else if (nums[mid] < target) {
left = mid + 1;
}
}
return left; // while 终止的条件是 left == right,返回哪个都一样
}
2.4 右侧二分查找
/**
* 找到目标值的右侧边界,同上面一样,搜索区间都是左闭右开
*
* @param arr 输入数组
* @param target 要查找的值
* @return 找到目标值的右边界
*/
public static int rightBinarySearch(int[] arr,int target){
int left = 0;
int right = arr.length;
int mid = 0;
while(left < right){
mid = left + ((right - left) >> 1);
if(arr[mid] == target){
left = mid+ 1; // 与左侧二分查找的区别!
}else if(arr[mid] > target){
right = mid;
}else if (arr[mid] < target) {
left = mid + 1;
}
}
return left-1; // 与左侧二分查找的区别!
}
2.5 左右侧边界合并终极版(个人还是倾向分开记忆)
public int boundBinarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid = 0;
while (left <= right) {
mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定左侧边界
right = mid - 1;
}
}
// 左侧:最后要检查 left 越界的情况
//if (left >= nums.length || nums[left] != target) {
//return -1;
//}
//return left;
// 右侧:最后要检查 right 越界的情况
//if (right < 0 || nums[right] != target) {
//return -1;
//}
//return right;
}
2 插值查找
- 插值查找是在二分查找的基础上将mid值的计算方法做改进。其mid的计算是自适应计算。
- 注意:插值查找适用关键字分布比较均匀。对于关键字分布不均匀的情况下效果可能不如二分查找
- 改为:
// 二分查找
int mid = left + ((right - left) >> 1);
// 插值查找
int mid = left + (right-left) * (findVal - arr[left]) / (arr[right] - arr[left]);
3 斐波那契查找(黄金分割法)
- 斐波那契数列:{1,1,2,3,5,8,13,21,34,55…}
- 斐波那契数列性质:
- 斐波那契查找原理类似与二分查找。主要是改变mid值,mid位于黄金分割点附近,即 。其中F代表斐波那契数列。
- F[k]-1=F[k-1]-1+F[k-1]-1+1,其中末尾的1代表mid的位置。该式子说明顺序表的长度为F[k-1]-1,纳闷这个顺序表永远都可分为F[k-1]-1和F[k-2]-1两段。故mid=low+F[k-1]-1;
- 但是顺序表的长度n不一定刚好等于F[k]-1,所以需要将原来循序表长度n增加到F[k]-1的长度。故F[k]-1>n即可。
/**
* 定义斐波那契额数组
* @param arrLength 斐波那契数组长度
* @return 斐波那契数组
*/
public static int[] fibonacciArr(int arrLength) {
int[] fibArr = new int[arrLength];
fibArr[0] = 1;
fibArr[1] = 1;
for (int i = 2; i < arrLength; i++) {
fibArr[i] = fibArr[i - 1] + fibArr[i - 2];
}
return fibArr;
}
public static int fibonacciSearch(int[] arr, int findVal) {
int left = 0;
int right = arr.length - 1;
int mid = 0;
// 斐波那契分割下标
int fibK = 0;
// 默认斐波那契数组长度为20
int[] fibArr = fibonacciArr(20);
// 获取斐波那契分割数值的下标
while (right > fibArr[fibK] - 1) {
fibK++;
}
// fibArr[k] 值可能大于arr的长度,因此我们需要使用Arrays类构造一个新的数组,并指向temp[]
int[] temp = Arrays.copyOf(arr, fibArr[fibK]);
// 实际上需求使用arr数组最后的数填充temp
for (int i = right + 1; i < temp.length; i++) {
temp[i] = arr[right];
}
// 使用while循环处理,找到findVal
while (left <= right) {
mid = left + fibArr[fibK - 1] - 1;
if (findVal < temp[mid]) {
// 查找值在左侧
right = mid - 1;
// 对应F[k-1]-1,所以是自减一
fibK--;
} else if (findVal > temp[mid]) {
// 查找值在右侧
left = mid + 1;
// 对应F[k-2]-1,所以是自减二
fibK -= 2;
} else {
if (mid <= right) {
return mid;
} else {
return right;
}
}
}
return -1;
}