查找算法
查找定义:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。
Pi:查找表中第i个数据元素的概率。
Ci:找到第i个数据元素时已经比较过的次数。
1. 顺序查找
当查找不成功时,需要n+1次比较,时间复杂度为O(n);
public class SeqSearch { public static void main(String[] args) { int[] arr ={45,42,13,123,78,13}; System.out.println(seqSearch(arr,13)); } public static int seqSearch(int[] arr,int value){ for (int i = 0; i < arr.length; i++) { if (arr[i] ==value){ return i; } } return -1; } }
2. 二分查找
说明:元素必须是有序的,如果是无序的则要先进行排序操作。
基本思想:也称为是折半查找,属于有序查找算法。用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。
复杂度分析:最坏情况下,关键词比较次数为log2(n+1),且期望时间复杂度为O(log2n);
注:折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,折半查找能得到不错的效率。但对于需要频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。——《大话数据结构》
1.递归方式
public class BinarySearch { public static void main(String[] args) { int arr[] = {1,8,10,89,1000,1000,1234}; ArrayList<Integer> list = binarySearch2(arr, 0, arr.length - 1, 10001); System.out.println("list= " +list); } //思路 /*1.首先确定数组中间的下标*/ /*2.需要查找的数进行比较*/ /*2.1当大于时,在中轴的右边进行递归*/ /*2.2小于时,在中轴的做左边进行递归*/ /*2.3等于是=时直接返回*/ //什么时候结束递归 /*1.找到时结束递归*/ /*2.递归完所有的数没有找到,left>right需要退出*/ /** * * @description:TODO * @params:1.数组2.左索引3.右索引4.查找的值 * @return: * @author: 苏兴旺 * @time: 2020/3/12 21:29 */ public static int binarySearch(int[] arr,int left,int right ,int findVal){ int mid = (left+right)/2; int midVal = arr[mid]; /*1.找到时结束递归*/ /*2.递归完所有的数没有找到,left>right需要退出*/ if (left>right){ return -1; } if (findVal>midVal){//当大于时,在中轴的右边进行递归 return binarySearch(arr,mid+1,right,findVal); }else if (findVal<midVal){ return binarySearch(arr,left,mid-1,findVal); }else { return mid; } } /*升级版当数组中有重复值时*/ /*1.在找到值时进行:mid索引值左边扫描将下标放到集合 * 2.在找到值时进行:mid索引值右边扫描将下标放到集合*/ public static ArrayList<Integer> binarySearch2(int[] arr, int left, int right , int findVal){ int mid = (left+right)/2; int midVal = arr[mid]; ArrayList<Integer> list = new ArrayList<>(); /*1.找到时结束递归*/ /*2.递归完所有的数没有找到,left>right需要退出*/ if (left>right){ return new ArrayList<>(); } if (findVal>midVal){//当大于时,在中轴的右边进行递归 return binarySearch2(arr,mid+1,right,findVal); }else if (findVal<midVal){ return binarySearch2(arr,left,mid-1,findVal); }else { int temp = mid-1; while (true){//在找到值时进行:mid索引值左边扫描将下标放到集合 if (temp<0||arr[temp]!=findVal){ break; } list.add(temp); temp-=1; } list.add(mid); temp = mid+1; while (true){//mid索引值右边扫描将下标放到集合 if (temp>arr.length-1||arr[temp]!=findVal){ break; } list.add(temp); temp+=1; } } return list; } }
2.非递归方式
public class BinarySearch { public static void main(String[] args) { int [] arr = {1,3,8,10,67,100}; int index = binarySearch(arr,-8); System.out.println(index); } public static int binarySearch(int[] arr,int target){ int left = 0; int right = arr.length-1; while (left<=right){ int mid = (left+right)/2; if (arr[mid] ==target){ return mid; }else if (arr[mid]>target){ right = mid-1;//需要向左查找 }else { left = mid+1;//向右查找 } } return -1; } }
3. 插值查找
public class InsertSearch { public static void main(String[] args) { //int mid = left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left]) int arr[] = new int[100]; for (int i = 0; i < arr.length; i++) { arr[i] = i+1; } System.out.println(Arrays.toString(arr)); ArrayList<Integer> list = insertSearch(arr, 0, arr.length - 1, 2); System.out.println(list); } /*插值查找*/ public static ArrayList<Integer> insertSearch(int[] arr, int left, int right , int findVal){ int mid = left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left]); int midVal = arr[mid]; ArrayList<Integer> list = new ArrayList<>(); /*1.找到时结束递归*/ /*2.递归完所有的数没有找到,left>right需要退出*/ if (left>right || findVal<arr[0]||findVal>arr[arr.length-1]){ return new ArrayList<>(); } if (findVal>midVal){//当大于时,在中轴的右边进行递归 return insertSearch(arr,mid+1,right,findVal); }else if (findVal<midVal){ return insertSearch(arr,left,mid-1,findVal); }else { int temp = mid-1; while (true){//在找到值时进行:mid索引值左边扫描将下标放到集合 if (temp<0||arr[temp]!=findVal){ break; } list.add(temp); temp-=1; } list.add(mid); temp = mid+1; while (true){//mid索引值右边扫描将下标放到集合 if (temp>arr.length-1||arr[temp]!=findVal){ break; } list.add(temp); temp+=1; } } return list; } }
4. 斐波那契查找
在介绍斐波那契查找算法之前,我们先介绍一下很它紧密相连并且大家都熟知的一个概念——黄金分割。
黄金比例又称黄金分割,是指事物各部分间一定的数学比例关系,即将整体一分为二,较大部分与较小部分之比等于整体与较大部分之比,其比值约为1:0.618或1.618:1。
0.618被公认为最具有审美意义的比例数字,这个数值的作用不仅仅体现在诸如绘画、雕塑、音乐、建筑等艺术领域,而且在管理、工程设计等方面也有着不可忽视的作用。因此被称为黄金分割。
大家记不记得斐波那契数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89…….(从第三个数开始,后边每一个数都是前两个数的和)。然后我们会发现,随着斐波那契数列的递增,前后两个数的比值会越来越接近0.618,利用这个特性,我们就可以将黄金比例运用到查找技术中。
1)相等,mid位置的元素即为所求
2)>,low=mid+1;
3)<,high=mid-1。
斐波那契查找与折半查找很相似,他是根据斐波那契序列的特点对有序表进行分割的。他要求开始表中记录的个数为某个斐波那契数小1,及n=F(k)-1;
开始将k值与第F(k-1)位置的记录进行比较(及mid=low+F(k-1)-1),比较结果也分为三种
1)相等,mid位置的元素即为所求
2)>,low=mid+1,k-=2;
说明:low=mid+1说明待查找的元素在[mid+1,high]范围内,k-=2 说明范围[mid+1,high]内的元素个数为n-(F(k-1))= Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1个,所以可以递归的应用斐波那契查找。
3)<,high=mid-1,k-=1。
说明:low=mid+1说明待查找的元素在[low,mid-1]范围内,k-=1 说明范围[low,mid-1]内的元素个数为F(k-1)-1个,所以可以递归 的应用斐波那契查找。
public class FibonacciSearch { public static int maxSize = 20; public static void main(String[] args) { int arr[] = {1,8,10,89,1000,1234}; System.out.println(fibonacciSearch(arr,891)); } /*非递归方式形成斐波那契数列*/ 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; } /*非递归进行查找*/ public static int fibonacciSearch(int[] arr,int findVal){ int low =0; int high=arr.length-1; int k=0;//表示斐波那契分割值的下标 int mid=0; int f[] = fib();//获得斐波那契数列 //获得斐波那契数列的数值 while (high>f[k]-1){ k++; } //因为f【k】大于数值的数组的长度;需要拷贝原数组 int[] temp = Arrays.copyOf(arr,f[k]); /*将数组最后的数填充到temp*/ //{1,8,10,89,1000,1234}==>{1,8,10,89,1000,1234,1234,1234...} for (int i = high+1; i <temp.length ; i++) { temp[i] = arr[high]; } while (low<=high){//找到key mid=low+f[k-1]-1; if (findVal<temp[mid]){//需要向数组的前面进行查找 high=mid-1; k--;//1.全部元素=前面+后面 2. f[k] = f[k-1] + f[k-2]; //因为mid前有f[k-1]个元素,可以继续进行拆分f[k-1]=f[k-2] + f[k-3] //既可以在f【k-1】的前面进行查找 //可以在f【k-1-1】-1的前面进行查找 }else if (findVal >temp[mid]){ low = mid+1; /*1.全部元素=前面+后面 * 2.f[k] = f[k-1] + f[k-2] * 3.因为后面还有f[k-2]个元素 f[k-1]=f[k-3] + f[k-4]*/ k-=2; }else {//找到 if (mid<=high){ return mid; }else { return high; } } } return -1; } }