目录
【查找算法】
【顺序查找】
【二分查找】
【插值查找】
【斐波那契查找】
常用查找算法有4种:顺序查找(线性查找),二分查找,插值查找,斐波那契查找
遍历数据,匹配查找内容,找到返回其索引,没找到返回-1
package cn.dataStructureAndAlgorithm.demo.search;
public class 顺序查找_seqSearch {
public static void main(String[] args) {
int[] data=new int[]{1,2,3,4,5,6};
System.out.println(seqSearch(data,5));
}
/**
* 顺序查找
* @param data 查找的数据堆
* @param value 需要查找数据
* @return 查找到返回其索引,没找到返回-1;
*/
public static int seqSearch(int[] data,int value){
for (int i=0;i
4
二分查找要求数据堆是顺序的,二分查找依赖排序,二分查找采用递归完成,首先要确定传入数据的中间下标,然后将待查找数据与中间数据对比,小于就向左递归查找,大于就向右递归查找。当等于时,就找到了。返回数据索引。没找到时,通过递归终止条件 left>right 来返回-1。
代码实现:
package cn.dataStructureAndAlgorithm.demo.search;
public class 二分查找_binarySearch {
public static void main(String[] args) {
int[] data=new int[]{1,2,3,4,5,6};
System.out.println(binarySearch(data,0,5,4));
}
/**
* 二分查找
* @param data 数据堆
* @param left 左索引
* @param right 右索引
* @param findVal 待查找数据
* @return 查找到数据返回索引,反之返回-1
*/
public static int binarySearch(int[] data,int left,int right,int findVal){
if (left>right){//已遍历完数据,未找到匹配值,终止递归
return -1;
}
int mid=(left+right)/2;//求中轴
int midVal=data[mid];
if (findValmidVal){
return binarySearch(data,mid+1,right,findVal);//向右递归查找
}else {
return mid;//找到了,返回数据索引
}
}
}
3
上面的代码中,存在一个问题。当数据堆中存在多个相同的值(如:{1,2,3,4,4,4,5,6})时,上面的算法,只能找到一个。
原因在于,代码中当找到相同值时,没有进行左右同值判定。为了接受多组数据,修改代码的索引存储结构为ArrayList(动态数组)类型
代码实现:
package cn.dataStructureAndAlgorithm.demo.search;
import java.util.ArrayList;
public class 二分查找_binarySearch {
public static void main(String[] args) {
int[] data=new int[]{1,2,3,4,4,4,5,6};
System.out.println(binarySearch(data,0,5,4));
}
/**
* 二分查找
* @param data 数据堆
* @param left 左索引
* @param right 右索引
* @param findVal 待查找数据
* @return 查找到动态数组,反之返回空动态数组
*/
public static ArrayList binarySearch(int[] data, int left, int right, int findVal){
if (left>right){//已遍历完数据,未找到匹配值,终止递归
return new ArrayList<>();
}
int mid=(left+right)/2;//求中轴
int midVal=data[mid];
if (findValmidVal){
return binarySearch(data,mid+1,right,findVal);//向右递归查找
}else {
//找到了,返回索引其动态数组
ArrayList list=new ArrayList();//创建动态数组
//开始向左搜索
int temp=mid-1;//临时索引
while (true){//向左判断是否有残留同值
if (temp<0 || data[temp]!=findVal){//下标越界或无同值就跳出
break;
}
list.add(temp);
temp--;
}
//保存中间已匹配到的数据
list.add(mid);
temp=mid+1;
while (true){//向右判断是否有残留同值
if (temp>data.length-1 || data[temp]!=findVal){//下标越界或无同值就跳出
break;
}
list.add(temp);
temp++;
}
return list;
}
}
}
[3,4,5]
优化后的代码,已经支持多位同值查询了,但仍然有一个不完善的地方,比如{1,2,3,4,....,100}这样100个值的数列,当要查找“1”时,二分查找会递归6次。有没有算法可以减少递归次数,快速找到目标值?有的,就是接下来的二分查找升级版----插值查找
相较于二分查找,插值查找对二分查找的变动就两处:
1,修改了mid的获取算法:二分查找中为 (left+right)/2 ,插值查找中改进为 left+(right-left)*(findVal-data[left])/(data[right]-data[left]) 看起来很麻烦,其主要是通过将要查找数据的大小与数列中最小数据的差值与整个数列差值进行比例计算,获取一个要查找数据的相对位置(非等差数列)或精确位置(等差数列),从而快速的接近查找值的位置,进而减少查找次数。
2,修改了退出递归的条件:采用上述算法计算mid值时,由于待查找值findVal也参与计算,当用户误输入(如:输入查找的数据不在数据堆范围内且很大)时,(right-left)*(findval-data[left]) 的值可能会变得巨大,从而计算得到的mid就会超出索引范围,导致下标越界。所以在退出递归条件中,应增加对输入查询数据大小的范围判断(findValdata[data.length-1]),以避免异常发生。
代码实现:
package cn.dataStructureAndAlgorithm.demo.search;
import java.util.ArrayList;
public class 插值查找_insertSearch {
public static void main(String[] args) {
int[] data=new int[100];
for (int i=0;i<100;i++){
data[i]=i+1;
}
System.out.println(insertSearch(data,0,data.length-1,1));
}
public static ArrayList insertSearch(int[] data, int left, int right, int findVal){
if (left>right || findValdata[data.length-1]){//已遍历完数据,未找到匹配值或不存在对应大小值,终止递归
return new ArrayList<>();
}
int mid=left+(right-left)*(findVal-data[left])/(data[right]-data[left]);//求中轴
int midVal=data[mid];
if (findValmidVal){
return insertSearch(data,mid+1,right,findVal);//向右递归查找
}else {
//找到了,返回索引其动态数组
ArrayList list=new ArrayList();//创建动态数组
//开始向左搜索
int temp=mid-1;//临时索引
while (true){//向左判断是否有残留同值
if (temp<0 || data[temp]!=findVal){//下标越界或无同值就跳出
break;
}
list.add(temp);
temp--;
}
//保存中间已匹配到的数据
list.add(mid);
temp=mid+1;
while (true){//向右判断是否有残留同值
if (temp>data.length-1 || data[temp]!=findVal){//下标越界或无同值就跳出
break;
}
list.add(temp);
temp++;
}
return list;
}
}
}
在数据均匀的情况下,插值查找优于二分查找,但在数据不均匀时,不一定。
斐波那契数列:1,1,2,3,5,8,13,21... 其前一位/后一位的值无线逼近于 0.618 ,0.618被誉为黄金分割点。该算法就是以每一个数组的黄金分割点为mid将其分割为左右两个子数组,并进行二分查找。所以该查找又被称作黄金分割法查找。
算法分析:
1,计算并保存一个斐波那契序列的数组,方便以后取值。数组名记为f,例如f[1]=1,f[2]=1,f[3]=2,f[4]=3,f[5]=5,f[6]=8,f[7]=13,f[8]=21...
2,把有序数组的长度尽可能扩充到接近data.length=f[k]-1,k是满足条件的最小值,比如数组长度为13,那么就把它长度扩充到f[8]-1=20,所有在末尾添加的扩充元素都是原数组最后一个元素的复制品
3,找到mid元素,不断进行二分比较,直到找到目标元素为止,这一步的做法与折半查找一模一样,仅仅是计算mid的公式从(low+high)/2改为low+(f[k-1]-1)
4,再找到匹配的数据后,由于原数组被加长过,可能会出现mid处于加长部分的情况,但这部分实际在原数组中不存在,好在right仍然保存着原数组的有效边界,且加长空间内容都是最后一个数据,所以返回的索引应该在mid与right中谁小取谁
为什么需要把数组长度扩充到f[k]-1而不是f[k]或者f[k+1]?
这是为了能正确递归计算mid值,看下图可发现 f[k]-1 = (f[k-1] + f[k-2]) - 1 = (f[k-1]-1) + 1 + (f[k-2]-1),中间的1就是我们二分的锚点mid,如果目标在左区,数组长度就缩到(f[k-1]-1),如果在右区,数组长度就缩到(f[k-2]-1),否则就等于mid完成查找。而左区 (f[k-1]-1)又能拆成 (f[k-2]-1)+1+(f[k-3]-1),右区 (f[k-2]-1)又能被拆成 (f[k-3]-1)+1+(f[k-4]-1) 这样递归分割下去就能不断的缩小区间直至找到目标。
“-1”的话,由于 f[k]-1可被化为 (f[k-1]-1) + 1 + (f[k-2]-1), 这样刚好有中间一个“1”作为锚点mid ,其他两边作为子数列的长度。
代码实现:
package cn.dataStructureAndAlgorithm.demo.search;
import java.util.ArrayList;
import java.util.Arrays;
public class 斐波那契查找_fibSearch {
public static final int SIZE=20;//定义斐波那契数组长度为20,不够用再加
public static void main(String[] args) {
int[] data=new int[]{1,2,3,4,5,6};
System.out.println(fibSearch(data,0,data.length-1,6));
}
public static int[] getFib(int[] data){
int f[]=new int[SIZE];
f[0]=f[1]=1;
for (int i=2;i f[k]-1){//找到满足f[K]-1>=data.length的最小值
k++;
}
int[] temp= Arrays.copyOf(data,f[k]);//对原有数据进行扩容,扩容空间数据为0
int index;
for (int i=data.length;itemp[mid]){//需要向右查找
left=mid+1;
k-=2;//mid=left+f[k-1-2]-1
}else {
//由于原数组被加长过,可能会出现mid处于加长部分,但这部分实际在原数组中不存在,
// 好在right仍然保存着原数组的有效边界,且加长空间内容都是最后一个数据,所以mid与right中谁小取谁
if (mid<=right){
return mid;
}else {
return right;
}
}
}
return -1;
}
}
5
【数据结构与算法整理总结目录 :>】<-- 宝藏在此(doge)