程序员常用的查找算法
查找算法
要求: 从数组中找出指定的关键值(key),常用的查找算法有4种:
(1)线性查找,也称为顺序查找
(2)二分查找
(3)插值查找
(4)斐波那契查找
说明: (2)、(3)、(4)本质上都是通过数组的中间值,将关键字(key)逐渐缩小查找范围,二分查找以数组中间值将查找范围缩小一半;插值查找也是通过某个中间值来缩小查找范围,但是中间值是动态变化的,根据插值公式计算;同理斐波那契查找算法是通过斐波那契数列关系推导出中间值,从而缩小关键值的查找范围。
线性查找
遍历数组,逐个比较,适用于小规模数据的查找,查找效率慢!
要求: 任意数组
Java代码实现
public int seqSearch(int[] array,int key){
for(int i=0;i<array.length;i++){
if(array[i]==key){
return i;
}
}
return -1;
}
二分查找
二分查找通过有序数组的中间元素将数组分成两部分,每次查找过程中将关键值key锁定在一部分中,从而缩小了一半的查找空间。
要求: 有序数组
public int binarySearch(int[] array,int left,int right,int key){
//递归的终止条件
if(left>right){
return -1;
}
int mid=(left+right)/2;
if(key<array[mid]){
return binarySearch(array,left,mid-1);
}else if(key>array[mid]{
return binarySearch(array,mid+1,right);
}else{
return mid;
}
}
//非递归版本
public int binarySearch2(int[] array,int left,int right,int key){
while(left<=right){
int mid=(left+right)/2;
if(key<array[mid]){
//key在[left,mid-1];
right=mid-1;
}else if(key>array[mid]{
//key在[mid+1,right]
left=mid+1;
}else{
return mid;
}
}
}
插值查找
本质上和二分查找一样,只是不再固定中间元素mid,mid的选取是动态变化,根据插值函数变化:\(mid=low+\frac{key-array[low]}{array[high]-array[low]}(high-low)\)
注意: 使用该公式时,需要保证:array[0]<=key<=array[array.length-1];否则可能会产生索引越界异常;
要求: 有序数组
java代码
public int insertSearch(int[] array,int left,int right,int key){
if(left>right || key<array[left] || key>array[right]){
return -1;
}
int=mid=left+((key-array[left])/(array[right]-array[left]))*(right-left);
if(key<array[mid]){
return insertSearch(array,left,mid-1);
}else if(key>array[mid]{
return insertSearch(array,mid+1,right);
}else{
return mid;
}
}
需求: 不仅要找到关键值,当数组中有多个关键值的时候,求出他们的索引位置?
思路分析:
(1)当找到一个关键值索引时候,不要立即返回,应该分别从该索引处向左、向右分别寻找,直到所有的关键值都找到为止,可以将中间值缓存在一个ArrayList中,当查询所有完成以后一并返回即可;
java代码
public List<Integer> insertSearch(int[] array,int left,int right,int key){
if(left>right || key<array[left] || key>array[right]){
return new ArrayList();
}
int=mid=left+((key-array[left])/(array[right]-array[left]))*(right-left);
if(key<array[mid]){
return insertSearch(array,left,mid-1);
}else if(key>array[mid]{
return insertSearch(array,mid+1,right);
}else{
//找到了;
ArrayList<Integer> resList=new ArrayList<Integer>();
//向当前值的左边寻找
int tmp=mid-1;
while(tmp>=0 && array[tmp]==key){
resList.add(tmp);
tmp--;
}
//将当前值索引添加进入resList
resList.add(mid);
//向当前值的右边寻找
int tmp=mid+1;
while(tmp<=array.length-1 && array[tmp]==key){
resList.add(tmp);
tmp++;
}
return resList;//此时返回并结束
}
}
斐波那契查找
(1)黄金分割点。黄金分割点将一条线段分成两部分,使得一部分线段长度与整条线段长度的比值等于另一部分线段长度与该部分线段长度的比值,比值为0.618
(2)斐波那契数列{1,1,2,3,5,8,13,21,35,},前两个数之和为第三个数,并且相邻两个数之比趋近于黄金分割点。
原理: 斐波那契查找算法,不再通过插值公式确定中间点mid,而是通过斐波那契数数列关系来获得mid,这也是一个动态获取的过程,那斐波那契数列与mid究竟有怎样的关系呢?或者怎样让他们发生关系呢???
废话不多说,先上图:
mid=low+F[k-1]-1
简单推导过程
(1)由于斐波那契数满足:\(F[k]=F[k-1]+F[k-2],k>=2\);
(2)等式两边同时-1,即:\(F[k]-1=(F[k-1]-1)+(F[k-2]-1)+1,k>=2\),如上图所示:一个数组长度为F[k]-1的数组array,可以划分为三个部分:[low,F[k-1]-1-1],[mid],[mid+1,F[k-2]-1-1];即:mid=low+F[k-1]-1
有了上面的公式,我们想要得到mid,首先需要得到斐波纳契数表达式F[k],根据上图可以知道:array.length-1=F[k]-1;而F[k]属于离散型的数列,不一定能保证其值恰好等于array.length-1,但是我们必要要保证F[k]-1>=arry.length即可符合要求,通过程序很他容易求得k的值。
要求: 有序数组
//获取符合斐波那契函数特征的数组,并指定数组的最大个数
public static int[] fibonacci(int maxSize){
int[] fib=new int[maxSize];
fib[0]=1;
fib[1]=1;
for(int i=2;i<maxSize;i++){
fib[i]=fib[i-1]+fib[i-2];
}
return fib;
}
public int fibonacciSearch(int[] array,int key){
int low=0;
int high=array.length-1;
int k=0;
int[] fib=fibonacci(array.length);
while(fib[k]-1<high){
k++
}
//此时k的取值刚好使得fib[k]-1>=high,
//fib[]多余的部分使用array数组的最高位来填充
int[] tmp=Arrays.copyOf(array,fib[k])
for(int i=high+1;i<tmp.length;i++){
//tmp的多余部分使用Array.copyOf()函数是默认填充0,我们需要填充arry的最高位
tmp[i]=array[high];
}
//根据斐波那契公式求mid,k已经知道
while(low<=high){
int mid=low+fib[k-1]-1//由上述推导公式可得
//二分查找开始
if(key<array[mid]){
//key在[low,mid-1]
high=mid-1;
//继续根据fib[]查找[low,mid-1]部分的mid
k=k-1;
//为什么是k=k-1呢?
//说明:
//fib[k]-1=(fib[k-1]-1)+1+(fib[k-2]-1)
//由于fib[k-1]=fib[k-2]+fib[k-3]即:
//(fib[k-1]-1)=(fib[k-2]-1)+1+(fib[k-3]-1);k由k-1到k-2;执行k--即可
}else(key<array[mid]){
//key在[mid+1,high]
low=mid+1;
k=k-2;
//为什么-2,原理同上,k-1到k-3中间需要执行k-2的操作
}else{
//找到,就是mid
return mid;
}
}
return -1;
}