查找算法及应用
常用查找算法包括顺序/线性查找、二分/折半查找、插值查找、斐波那契查找。
-
线性查找
逐一比对,发现有相同值返回即可。
-
二分查找
前提是数组有序。如果数据是连续的情况下可以使用插值查找。
-
插值查找
插值查找算法类似二分查找,不同的是插值查找每次从自适应中间位置处开始查找。数据比较连续情况下实用。将折半查找中的求mid索引的公式改进,low表示左边索引left,high表示右边索引right,key表示查找的值。
m i d = l o w + h i g h 2 = l o w + 1 2 ( l o w + h i g h ) mid = \frac{low+high}{2} = low + \frac{1}{2}(low + high) mid=2low+high=low+21(low+high)m i d = l o w + k e y − a [ l o w ] a [ h i g h ] − a [ l o w ] ( h i g h − l o w ) mid = low + \frac{key - a[low]}{a[high] - a[low]}(high - low) mid=low+a[high]−a[low]key−a[low](high−low)
-
斐波那契查找
黄金分割点是指把一条线段分成两段,使其中一部分与全长之比等于另一部分与这一部分之比,取其前三位数字的近似值是0.618。斐波那契数列的两个相邻数的比例,无线接近黄金分割值0.618。斐波那契查找原理是改变中间节点mid的位置,mid=low+F(k-1)-1,其中F代表斐波那契数列。
/**
* 抽象父类
*/
public abstract class SearchParent {
protected static int[] arr = {1, 8, 10, 89, 1000, 1000, 1000, 1234};
//在数组中查找指定值的位置
abstract int search(int[] arr, int val);
}
/**
* 线性查找
*/
public class SimpleSearch extends SearchParent {
public static void main(String[] args) {
System.out.println("89的下标是:" + new SimpleSearch().search(arr, 89));
}
@Override
int search(int[] arr, int val) {
for (int i=0;i<arr.length;i++){
if (val == arr[i]) {
return i;
}
}
return -1;
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 折半查找
*/
public class BinarySearch extends SearchParent {
public static void main(String[] args) {
integerOverflow();
System.out.println("The index of 1000 is " + new BinarySearch().search(arr, 1000));
}
@Override
int search(int[] arr, int val) {
int left = 0;
int right = arr.length - 1;
return binarySearch(arr, left, right, val);
}
//只能查找到第一个匹配的值
private static int binarySearch(int[] arr, int left, int right, int val){
int mid = (left + right)/2;
int tmp = arr[mid];
if(left > right){
return -1;
}
if(val > tmp){
return binarySearch(arr, mid+1, right, val);
}else if(val < tmp){
return binarySearch(arr, left, mid-1, val);
}else{
return mid;
}
}
//折半查找非递归方式实现
private static int binarySearch(int[] arr, int val){
int left = 0;
int right = arr.length - 1;
while(left <= right){
int mid = (left + right)/2;
if(arr[mid] > val){
right = mid - 1;
}else if(arr[mid] < val){
left = mid + 1;
}else{
return mid;
}
}
return -1;
}
//查找所有符和的值
private static List<Integer> binary2Search(int[] arr, int left, int right, int val){
int mid = (left + right)/2;
int tmp = arr[mid];
if(left > right){
return null;
}
if(val > tmp){
return binary2Search(arr, mid + 1, right, val);
}else if(val < tmp){
return binary2Search(arr, left, mid-1, val);
}else{
List<Integer> result = new ArrayList<>(arr.length);
int temp = mid-1;
while(temp >= 0 && arr[temp] == val) {
result.add(temp);
temp--;
}
result.add(mid);
temp = mid + 1;
while(temp <= arr.length-1 && arr[temp] == val){
result.add(temp);
temp++;
}
return result;
}
}
//获取中间索引可能会出现整数溢出
private static void integerOverflow(){
int l = 0;
int r = Integer.MAX_VALUE - 1;
int m = (l+r)/2;
System.out.println(m);
l = m + 1;
//m = (l+r)/2; 会导致整数溢出
//m = l + (r-l)/2; 除法效率较差
m = (l+r)>>>1;
System.out.println(m);
}
}
/**
* 插值查找
*/
public class InsertValSearch extends SearchParent {
public static void main(String[] args) {
System.out.println("1000的下标是:" + new InsertValSearch().search(arr, 1000));
}
@Override
int search(int[] arr, int val) {
int left = 0;
int right = arr.length - 1;
return insertSearchValue(arr, left, right, val);
}
private int insertSearchValue(int[] arr, int left, int right, int val) {
if(left > right || val < arr[0] || val > arr[arr.length-1]){
return -1;
}
int mid = left + (right-left)*(val-arr[left])/(arr[right]-arr[left]);
int temp = arr[mid];
if(val > temp){
return insertSearchValue(arr, mid+1, right, val);
}else if(val < temp){
return insertSearchValue(arr, left, mid+1, val);
}else{
return mid;
}
}
}
import java.util.Arrays;
/**
* 斐波那契查找
*/
public class FibonacciSearch extends SearchParent {
private static final int MAXSIZE = 20;
public static void main(String[] args) {
System.out.println(new FibonacciSearch().search(arr, 1000));
}
//生成斐波那契数组
private int[] fib(){
int[] f = new int[MAXSIZE];
f[0] = 1;
f[1] = 1;
for(int i=2;i<MAXSIZE;i++){
f[i] = f[i-2] + f[i-2];
}
return f;
}
@Override
int search(int[] arr, int val) {
int low = 0;
int high = arr.length - 1;
int k = 0; //表示斐波那契分割值的下标
int mid = 0;
int[] f = fib(); //获取斐波那契分割数值的下标
//找到数组长度对应的斐波那契数列中对应的元素F(n)的值
while(high > f[k] - 1){
k++;
}
int[] temp = Arrays.copyOf(arr, f[k]); //F(k)的值可能大于arr的长度,不足部分使用0填充
//使用数组的最后一个数填充
for(int i=high+1;i<temp.length;i++){
temp[i] = arr[high];
}
while(low <= high){
mid = low + f[k-1] - 1;
if(val < temp[mid]){
//全部元素=前面元素+后面元素,f[k]=f[k-1]+f[k-2]
//因为前面有f[k-1]个元素,所以可以继续拆分f[k-1]=f[k-2]+f[k-3]
high = mid - 1;
k-=1;
}else if(val > temp[mid]){
low = mid + 1;
//因为后面有f[k-2]个元素,所以可以继续拆分
k-=2;
}else{
if(mid <= high){ //如果是原查找表中的元素
return mid;
}else{
return high; //如果是填充值
}
}
}
return -1;
}
}