常用查找算法
Java中常用的查找算法有四种:
- 线性查找
- 二分查找
- 插值查找
- 斐波那契(黄金分割)查找
线性查找
线性查找也就是顺序查找,它的思路很简单:将要查找的元素与数组中的元素依次比较,找到相同的就返回该索引。
二分查找
二分查找需要待查找的数组是有序的才可以使用该算法。其思路就是从待查找数组的中间位置取值与查找的数做比较,由于待查找数组有序,所以查找的数只有三种可能,
- 查找的数等于数组中间位置的数;
- 查找的数在数组左侧到中间位置的区间;
- 查找的数在数组右侧到中间位置的区间。
根据这种算法可以较快的缩小查找的范围。代码实现可以使用非递归的方法和递归方法。
非递归实现二分查找
public static int binarySearch1(int findVal,int[] arr){
int low =0;
int high=arr.length-1;
while(high>=low){
int index=(low+high)/2;
if(findVal<arr[index]){
high=index-1;
}else if(findVal>arr[index]){
low=index+1;
}else return index;
}
return -1;
}
递归实现二分查找
public static int binarySearch2(int findVal, int[] arr, int low, int high) {
if(low>high){
return -1;
}
int index = (low+high)/2;
int indexVal = arr[index];
if(findVal>indexVal){
return binarySearch2(findVal,arr,index+1,high);
}else if(findVal<indexVal){
return binarySearch2(findVal,arr,low,index-1);
}else {
return index;
}
}
入果查找的数在数组中不止一个的话,可以考虑将多个索引存入集合中,最后输出。
插值查找
插值查找算法与二分查找类似,只是在确定中间位置索引时有所区别。
二分查找:
m
i
d
=
l
e
f
t
+
(
l
e
f
t
+
r
i
g
h
t
)
/
2
mid=left+(left+right)/2
mid=left+(left+right)/2;
插值查找:
m
i
d
=
l
e
f
t
+
(
r
i
g
h
t
−
l
e
f
t
)
∗
(
f
i
n
d
V
a
l
−
a
r
r
[
l
e
f
t
]
)
/
(
a
r
r
[
r
i
g
h
t
]
−
a
r
r
[
l
e
f
t
]
)
mid=left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left])
mid=left+(right−left)∗(findVal−arr[left])/(arr[right]−arr[left]);
这样能够更快向要查找数靠近。其在对于数据量大,元素分布均匀的数据速度快。
public static int insertValueSearch(int[]arr,int findVal,int left,int right){
if(left>right ||findVal<arr[left] ||findVal>arr[right]){
return -1;
}
int mid = left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left]);
int midVal = arr[mid];
if(findVal>midVal){
return insertValueSearch(arr,findVal,mid+1,right);
}else if(findVal<midVal){
return insertValueSearch(arr,findVal,left,mid-1);
}else {
return mid;
}
}
可以看到插入查找的代码与上面二分查找代码十分相似,除了mid那里有改动外,在跳出循环那里还要多判断查找的值与数组首尾的值大小,如果知道不在数组中就不需要接下来的递归操作了,否则很可能造成mid值超出数组索引范围,产生数组越界的异常。
斐波那契查找
该查找算法与上述两种查找算法也很类似,也是对于mid的选取做了相应的变化。斐波那契查找顾名思义,就是根据斐波那契数列的性质来确定mid值。斐波拉契数列的通项公式是:
f
(
k
)
=
f
(
k
−
1
)
+
f
(
k
−
2
)
,
k
>
2
f(k)=f(k-1)+f(k-2),k>2
f(k)=f(k−1)+f(k−2),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分割开,如图:
将要查找的数组长度扩展成最接近的斐波那契数大小,再按图分割,mid=low+f(k-1)-1;在使用斐波那契查找时,肯定需要先得到一个斐波那契数列,具体代码如下:
import java.util.Arrays;
public class FeibonaciSearch {
private static int maxSize=10;
public static void main(String[] args) {
int[] arr= {1,6,8,10,21,66,100};
System.out.println(Arrays.toString(arr));
System.out.println(feibonaciSearch(arr,21));
}
//生成斐波那契数列
public static int[] feibonaci(int n){
int[] f= new int[n];
f[0]=1;
f[1]=1;
for(int i=2;i<n;i++){
f[i]=f[i-1]+f[i-2];
}
return f;
}
//斐波那契查找
public static int feibonaciSearch(int[] arr,int findVal){
int left = 0;
int right=arr.length-1;
int k=0; //用来计数是斐波那契数列中第几个数
int f[]=feibonaci(maxSize);
//找到与查找数组arr长度匹配的斐波那契数,
//但是斐波那契数有可能大于数组长度
while (right>f[k]-1){
k++;
}
//斐波那契数大于数组长度,要将arr数组补充到斐波那契数的长度
int[] temp = Arrays.copyOf(arr,f[k]-1);
//再将补充数组填充的0全部替换成原数组最后一个值
for(int i=right+1;i<temp.length;i++){
temp[i]=arr[right];
}
//此时原数组已经准备好可以使用斐波那契分割
while (left<=right){
int mid = left+f[k-1]-1;
if(findVal>temp[mid]){
left=mid+1;
k-=2; //此时值落在分割的右边,新的长度就是f[k-2]-1;
}else if(findVal<temp[mid]){
right=mid-1;
k--;
}else { //找到查找的数,但是由于前面对原数组进行了扩充
//因此需要返回小的下标索引;
if(mid<=right){
return mid;
}else
return right;
}
}
return -1; //没找到返回-1;
}
}
注意:以上除了线性顺序查找,其他三种查找算法都需要保证查找的数组是 有序 的。