查找算法
1.二分查找
二分查找必须对有序数组进行,每次先找到中间值进行比较,若大于当前值则向后继续按此方法找,若小于当前值则向前继续按此方法找。
二分查找的思路:
1.首先确定数组中间值:
mid = (left + right) / 2;
2.然后比较中间值mid和要查找的值num大小;
2.1若num>arr[mid],则向右递归;
2.2.若num<arr[mid],则向左递归;
2.3.若num=arr[mid],则返回值;
3.当left>right时结束递归;
package com.sratct.search;
import java.util.ArrayList;
import java.util.List;
/**
* 二分查找
*/
public class BinarySearch {
public static void main(String[] args) {
int[] arr = {1, 5, 20, 60,60, 81};
System.out.println(binarySearch(arr,0, arr.length-1, 61));
System.out.println(binarySearch1(arr,0, arr.length-1, 61));
}
/**
* 在一个有序数组中查找到一个数并返回下标
* @param arr
* @param left
* @param right
* @param num
* @return
*/
public static int binarySearch(int[] arr, int left, int right, int num) {
int mid = (left + right) / 2;
if (left <= right) {
if (num < arr[mid]) { // 需要查找的值小于中间值
// 左边递归
return binarySearch(arr, left, mid-1, num);
} else if (num > arr[mid]) { // 需要查找的值大于中间值
// 右边递归
return binarySearch(arr,mid+1, right, num);
} else {
return mid;
}
}
return -1;
}
/**
* 在一个有序数组中查找一个数并返回所有相同的数的下标
* @param arr
* @param left
* @param right
* @param num
* @return
*/
public static ArrayList<Integer> binarySearch1(int[] arr, int left, int right, int num) {
int mid = (left+right)/2;
if (left <= right) {
if (num < arr[mid]) { // 需要查找的值小于中间值
// 左边递归
return binarySearch1(arr, left, mid-1, num);
} else if (num > arr[mid]) { // 需要查找的值大于中间值
// 右边递归
return binarySearch1(arr,mid+1, right, num);
} else {
// 当找到值以后,向左向右查找到相同的值,下标存入集合
List<Integer> integers = new ArrayList<>();
// 向左边查找
int temp = mid - 1;
while (true) {
if (temp < 0 || arr[temp] != num) {
break;
}
integers.add(temp);
temp -=1; //temp 左移
}
// 中间值
integers.add(mid);
// 右边
temp = mid + 1;
while (true) {
if (temp > arr.length-1 || arr[temp] != num) {
break;
}
integers.add(temp);
temp +=1; //temp右移
}
return (ArrayList<Integer>) integers;
}
}
return null;
}
}
2.插值查找
1)插值查找类似于二分查找,不同的是插值查找每次自适应mid处开始查找;
2)左边所有left,右边所有right,key为要查找的值num;
二分查找和插值查找公式:
mid = (left + high) / 2 = left + 1/2(right-left); // 二分查找
mid = left + ((num - arr[left]) / (arr[right] - arr[left]) )* (right - left); //插值查找
package com.sratct.search;
/**
* 插值查找
*/
public class InsertValueSearch {
public static void main(String[] args) {
int[] arr = new int[100];
for (int i=0; i<arr.length; i++) {
arr[i] = i+1;
}
System.out.println(insertValueSearch(arr,0,99,0));
}
// 插值查找
public static int insertValueSearch(int[] arr, int left, int right, int num) {
// 防止数组越界:num > arr[0] || num > arr[arr.length-1]
if (left <= right || num > arr[0] || num > arr[arr.length-1]) {
int mid = left + ((num - arr[left]) / (arr[right] - arr[left])) * (right - left);
if (num > arr[mid]) {
return insertValueSearch(arr, mid + 1, right, num);
} else if (num < arr[mid]) {
return insertValueSearch(arr, left, mid - 1, num);
} else {
return mid;
}
}
return -1;
}
}
3.斐波那契(黄金分割法)查找算法
1)黄金分割点指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取前三位近似数值为0.618。
2)斐波那契数列{1,1,2,3,5,8,13,21,34,55}发现两个相邻的数之比近似为0.618。
3)工作原理:
斐波那契查找原理与前面两种相似,仅仅改变中间结点(mid)位置,此时mid为黄金分割点附近,
即mid = left + F(k-1)-1 (F为斐波那契数列),如图
对F(k-1)-1的理解:
1)有斐波那契数列F(k) = F(k-1) + F(k-2)的性质,可以得到(F(k)-1) = (F(k-1)-1) + (F(k-2)-1) +1,由此可得,只要顺序表的长度为F(k)-1,则可以将表分为长度为F(k-1)-1和F(k-2)-1两端,即如上图所示,从而中间位置为
mid = left + F(k-1) -1
- 类似的每一段都可以用相同方式分割;
3)但是顺序表的长度不一定为F(k)-1,所以需要将原来的顺序表长度n增加至F(k)-1,这里的k值只要能使得F(k)-1恰好大于等于n即可,有下面代码得到,顺序表长度增加后,新增的位置(从n+1到F(k)-1位置),都赋为n位置的值即可
while(n>flib(k)-1)
k++;