在最近的复习中,我复习三种常用的查找算法,它们分别是:
1 线性查找
2 二分查找
3 插值法
4 斐波那契查找
线性查找
首先我们进行线性查找的讨论,对于线性查找,我们所做的操作就遍历数组同时逐一比对找出相匹配的元素,具体代码如下
public int search(int[] arr,int value)
{
for(int i=0;i<arr.length;i++)
{
if(arr[i]==value)
{
return i;
}
}
return -1;s
}
又上述代码我们不难理解,线性查找就是遍历数组的同时逐一比对数组的值和要查找的值,如果相同则就返回对应的数组下标
二分查找
对于二分查找,我们首先计算mid的值,在此演示中,我们设置的mid值为(left+right)/2,如果要搜索的值大于数组下标为mid的值,则就将left的值设置为mid+1,继续遍历,如果要插入的值小于数组下标为mid的值,则就将right的值设置为mid-1,继续遍历,重复上述操作,直到找到对应的数组的下标为止,当发生left>right时,则说明数组中并没有要搜索的值,此时我们返回-1并结束循环。
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;
}
} //第二种方式是找出所有对应的数据才停止
public static List<Integer> binarySearch2(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 binarySearch2(arr,mid+1,right,findVal);
}else if(findVal<midVal)
{
return binarySearch2(arr,left,mid-1,findVal);
}else{
List<Integer> list=new ArrayList<Integer>();
int temp=mid-1;
while (true)
{
if(temp<0||arr[temp]!=findVal)
{
break;
}
//否则就把temp放入到集合中
list.add(temp);
temp-=1;
}
list.add(mid);
temp=mid+1;
while (true)
{
if(temp>arr.length-1||arr[temp]!=findVal)
{
break;
}
//否则就把temp放入到集合中
list.add(temp);
temp+=1;
}
return list;
}
}
在经过分析时,我们发现搜索边缘的操作对于二分查找太过繁琐,所以我们引入插值法来进行搜索
插值法
插值法与二分查找相同,不过mid值有所改变
/**
* 插值法和二分查找法一样,查找数组必须是有序的
* @param arr
* @param left
* @param right
* @param findVal
* @return
*/
public static int insertValueSearch(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]);
int midVal=arr[mid];
if(findVal>midVal)
{
return insertValueSearch(arr,mid+1,right,findVal);
}else if(findVal<midVal)
{
return insertValueSearch(arr,left,mid-1,findVal);
}else{
return mid;
}
}
与二分查找方法不同是,插值法的mid等于low+(high-left)*(findVal-arr[low])/(arr[high]-arr[low]),我们在此基础上继续查找边缘位,如有一个大小为100的数组,第一位是0,最后一位是100,我们使用该方法查找0 100时会发现,我们第一次求出的mid的值就是对应的下标,所以插值法简化了二分查找。
斐波那契查找
斐波那契查找与二分查找很相似,他是根据斐波那契序列的特点对有序表进行分割的。他要求开始表中记录的个数为某个斐波那契数小1,即n=F(k)-1;(n为数组的大小,如果数组大小小于F(k)-1,则我们会将数组填充,即拿数组最后一位填充,直到n大于等于F(k)-1时)
开始将k值与第F(k-1)位置的记录进行比较(及mid=low+F(k-1)-1),比较结果也分为三种
1)相等,mid位置的元素即为所求
2)key>arr[mid],low=mid+1,k-=2;说明:low=mid+1说明待查找的元素在[mid+1,hign]范围内,k-=2 说明范围[mid+1,high]内的元素个数为n-(F(k-1))= Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1个,所以可以递归的应用斐波那契查找
3) key<,arr[mid],high=mid-1,k-=1;说明:low=mid+1说明待查找的元素在[low,mid-1]范围内,k-=1 说明范围[low,mid-1]内的元素个数为F(k-1)-1个,所以可以递归的应用斐波那契查找
private static int maxSize=20;
public static void main(String[] args)
{
int[] arr={1,8,10,89,1000,1234};
}
//因为后面我们的mid=low+F(k-1)-1,需要使用斐波那契数列,因此我们需要先获取到一个斐波那契数列
//非递归方法获取斐波那契数列
public static int[] fib()
{
int[] f=new int[maxSize];
f[0]=1;
f[1]=1;
for(int i=2;i<maxSize;i++)
{
f[i]=f[i-1]+f[i-2];
}
return f;
}
//编写斐波那契查找算法
//使用非递归的方式编写算法
/**
*
* @param a 数组
* @param key 我们需要的关键码(值)
* @return 返回对应的下标,如果没有返回-1
*/
public static int fibSearch(int[] a,int key)
{
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来循环处理,找到我们的数key
while (low<=high)
{
//只要这个条件满足就可以找
mid=low+f[k-1]-1;
if(key<temp[mid])
{
//我们应该继续向数组的左边查找
high=mid-1;
//为什么是k--
//说明:
//1 全部的元素=前面的元素+后面的元素
//2 f[k]=f[k-1]+f[k-2]
//因为前面有f[k-1]个元素,所以我们可以继续拆分
//即在f[k-1]的前面继续查找,即下次循环mid=f[k-1-1]-1
k--;
}else if(key>temp[mid])
{
//说明我们应该向数组的右边进行查找
low=mid+1;
//说明:
//1 全部的元素=前面的元素+后面的元素
//2 f[k]=f[k-1]+f[k-2]
//3 因为后面有f[k-2]个元素
//即在f[k-2]的前面可以继续进行查找k-=2
//即下次循环mid=f[k-1-2]-1
k-=2;
}else{
//需要确定返回的是哪个下标
if(mid<high)
{
return mid;
}else {
return high;
}
}
}
return -1;
}