十、查找算法
常用的查找算法:
- 顺序(线性)查找
- 二分查找/折半查找
- 插值查找
- 斐波那契查找
10.1 线性查找
- 一个数列可有序,可无序
代码实现
/**
* 线性查找
* 这里是找到一个即返回
* @param arr 查找的数据数列
* @param val 需要查找的值
* @return
*/
public static int seqSearch(int[] arr,int val){
//线性查找是逐一比对,发现相同值,返回下标
for (int i = 0; i < arr.length; i++) {
if (arr[i] == val){
return i;
}
}
return -1;
}
10.2 二分查找(Binary Search)
- 有序数列才可使用二分查找
思路分析
- 首先确定该数组的中间下标mid = (left + right)/ 2
- 然后让需要查找的数findVal和arr【mid】比较
- findVal > arr[mid],向右查询
- findVal < arr[mid],向右查询
- findVal == arr[mid],找到,返回
- 结束递归的条件
- 找到就结束
- 递归完整个数组,未找到,结束递归,left > right
基本写法
public static int binarySearch(int[] arr,int left,int right,int findVal){
if (left > right){
return -1;
}
//确定中间数组的下标
int mid = (left + right)/2;
int midVal = arr[mid];
//与中间数组比较
if (findVal > midVal){//向右查找
return binarySearch(arr,mid+1,right,findVal);
}else if (findVal < midVal){
return binarySearch(arr,left,mid-1,findVal);
}else {
return mid;
}
}
新需求
当一个数组中有多个相同的数值是 ,将所有数值都查到
代码实现
package com.why.search;
import com.sun.jdi.PathSearchingVirtualMachine;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Level;
/**
* @Description TODO 当一个数组中有多个相同的数值是 ,将所有数值都查到,使用二分查找
* @Author why
* @Date 2020/11/1 16:35
* Version 1.0
**/
public class NewBinarySearch {
public static void main(String[] args) {
int[] arr = {1,8,8,10,10,10,10,11};
List res = newBinarySearch(arr, 0, arr.length - 1, 8);
if (!res.isEmpty()){
System.out.println(res);
}else {
System.out.println("未找到");
}
}
/**
* 二分查找查找多条数据
*
* 思路:
* 找到mid值时,不要马上返回
* 向mid 索引值的左边扫描将所有满足查找值的元素下标加入到集合
* 向右扫描将所有满足查找值的元素下标加入到集合
* @param arr
* @param left
* @param right
* @param findVal
* @return
*/
public static List<Integer> newBinarySearch(int[] arr, int left, int right, int findVal){
if (left > right){
return new ArrayList<Integer>();
}
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal){
return newBinarySearch(arr,mid + 1,right,findVal);
}else if (findVal < midVal){
return newBinarySearch(arr,left,mid - 1,findVal);
}else {
List<Integer> resIndexList = new ArrayList<>();
//向左边扫描
int temp = mid - 1;
while (true){
if (temp < 0 || arr[temp] != findVal){
break;
}
resIndexList.add(temp);
temp -= 1;
}
//中间值
resIndexList.add(mid);
//向右扫描
temp = mid + 1;
while (true){
if (temp > arr.length || arr[temp] != findVal){
break;
}
resIndexList.add(temp);
temp += 1;
}
return resIndexList;
}
}
}
10.3 插值查找
基本原理
-
插值查找算法类似于二分查找算法,不同的是插值查找每次从自适应mid处开始查找
-
mid索引的公式,low表示左边索引,high表示右边索引,key表示查找的值
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 + (key - a[low]) / (a[high] - a[low]) * (high - low) mid=low+(key−a[low])/(a[high]−a[low])∗(high−low) -
插值索引,对应代码:
int mid = left + (right + left)*(findVal - arr[left]) / (arr[right] - arr[left]);
解决的问题
用于查找类似于[1,2,3,4,…,n]的数据序列中的值
代码实现
/**
* 插值查找算法
* @param arr
* @param left
* @param right
* @param findVal
* @return
*/
public static int insertSearch(int[] arr,int left,int right,int findVal){
if (left >right || findVal <arr[0] ||findVal > arr[arr.length - 1]){
return -1;
}
int mid = left + (right + left)*(findVal - arr[left]) / (arr[right] - arr[left]);
if (findVal < arr[mid]){
return insertSearch(arr,left,mid - 1,findVal);
}else if (findVal > arr[mid]){
return insertSearch(arr,mid + 1,right,findVal);
}else {
return mid;
}
}
10.4 斐波那契(黄金分割法)查找算法(Fibonacci Search)
黄金分割点
指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值0.618,此比例称为黄金分割,也称中外比
斐波那契数列
斐波那契数列{1,1,2,3,5,8,13,21,34,55},发现相邻两数的比例无限接近黄金分隔值0.618
基本原理
改变中间节点mid的位置,mid不再是中间节点,二十位于黄金分割点附近,即
m
i
d
=
l
o
w
+
F
[
k
−
1
]
−
1
mid = low + F[k - 1] - 1
mid=low+F[k−1]−1
F代表斐波那契数列,如图所示
对F(k - 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) + 1 F[k]−1=(F[k−1]−1)+(F[k−2]−1)+1
该式说明,只要顺序表的长度为F(k) - 1,则可将该表分为长度为F(k-1) - 1和F(k-2) - 1的两段,即如上图所示
m i d = l o w + F [ k − 1 ] − 1 mid = low +F[k-1] - 1 mid=low+F[k−1]−1 -
类似的,每一子段也可分割
-
顺序表长度不一定刚好等于F[k] - 1,故需将原来长度n增加至F[k] - 1,k使F[k] - 1刚好大于或等于n即可,新增的位置,都赋为n位置的值即可
有序数组才可使用此查找算法
代码实现
package com.why.search;
import com.sun.source.tree.IfTree;
import java.security.Key;
import java.util.Arrays;
/**
* @Description TODO 斐波那契查找算法
* @Author why
* @Date 2020/11/2 16:53
* Version 1.0
**/
public class FibonacciSearch {
public static int maxSize = 20;
public static void main(String[] args) {
int[] arr = {1,8,10,89,1000,1234};
int i = fibSearch(arr, 1000);
if (i == -1){
System.out.println("未找到");
}else {
System.out.println("下标是:"+i);
}
}
/**
* 需要使用斐波那契数列,首先获取到斐波那契数列
* 非递归方式
* @return
*/
public static int[] fib(){
int[] f = new int[maxSize];
f[0] = 1;
f[1] = 1;
for (int i = 2; i < f.length; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f;
}
/**
* 斐波那契查找算法
* 非递归
* @param a
* @param findVal
* @return
*/
public static int fibSearch(int[] a,int findVal){
int low = 0;
int high = a.length - 1;
int k = 0;//表示斐波那契分割数值的下标
int mid = 0;//存放mid值
//获取斐波那契数列
int[] f = fib();
//获取斐波那契分割数值的下标
while (high > f[k] - 1){
k++;
}
//因为f[k]值可能大于数组长度,
// 因此需要使用Arrays类,构造新的数组,并指向temp[]
//不足的部分使用0填充
int[] temp = Arrays.copyOf(a,f[k]);
//实际上需使用a数组最后 的数填充temp
for (int i = high + 1; i < temp.length; i++) {
temp[i] = a[high];
}
//循环处理查找数
while (low <= high){
mid = low +f[k-1] -1;
if (findVal < temp[mid]){//继续向数组左半部分查找
high = mid - 1;
//说明:
//1.全部元素 = 前面元素 + 后面元素
//2.F[k] = F[k-1] + f[k-2]
//因为前面有F[k-1]个元素,所以可以继续拆分
//F[k-1] = F[k-2] + F[k-3]
//即在F[k-1]前面继续查找,k--
k--;
}else if (findVal > temp[mid]){//向右查找
low = mid + 1;
k -= 2;
}else {//找到
//需要确定返回那个下标
if (mid <= high){
return mid;
}else {
return high;
}
}
}
return -1;//未找到
}
}