在java中,我们常用的查找算法有四种:
(1)顺序(线性)查找
(2)二分查找/折本查找
(3)插值查找
(4)斐波那契查找
8.1线性查找算法
有一个数列:{1,8,19,3,1000},判断数列中是否包含此名称【顺序查找】要求:如果找到了,就提示找到,并给出下标值
@思路:
遍历如果找到符合条件的值就返回
public class SeqSearch { public static void main(String[] args) { int[] arr={23,1,78,9,55}; int index=seqSearch(arr,78); if (index != -1){ System.out.println("目标值的下标为:"+index); }else { System.out.println("没有找到目标值"); } } /** * 此方法为找到一个目标值就返回 * @param arr * @param value * @return */ private static int seqSearch(int[] arr, int value) { for (int i=0;i<arr.length;i++){ if (arr[i] == value){ return i; } } return -1; } }
8.2二分查找算法
ps:二分查找是对有序数组进行查找
8.2.1二分查找思路分析
8.2.2实例
(1)请对一个有序数组进行二分查找 {1,8, 10, 89, 1000, 1234} ,输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"。
(2)说明:增加了找到所有的满足条件的元素下标:
课后思考题: {1,8, 10, 89, 1000, 1000,1234} 当一个有序数组中,有多个相同的数值时,如何将所有的数值都查找到,比如这里的 1000.
//二分查找的前提是数组是有序的 public class BinarySort { public static void main(String[] args) { int[] arr={1,2,2,2,34,67,88,123}; List<Integer> integers = binarySort2(arr, 0, arr.length - 1, 2); Collections.sort(integers); //对list集合中元素进行排序后输出 System.out.println(integers); //(list集合特点:有序可重复指的是输出顺序与插入顺序一致,不是数字之间的大小顺序) /*int i = binarySort(arr, 0, arr.length - 1, 2); System.out.println(i);*/ } /**二分查找(此方法参数一定要传入左右下标,因为需要递归进行局部遍历,左右下标会动态变化) * @param arr 传入待查找数组 * @param left 待比较数组的左下标 * @param right 待比较数组的右下标 * @param findVal 待查找值 */ public static int binarySort(int[] arr,int left,int right,int findVal){ if(right < left){ //遍历完数组,没有找到符合值 return -1; } int mid=(left+right)/2; //求出中间值下标 int midValue=arr[mid]; //求出中间值 if (findVal >midValue){ //比中间值大,向右递归 return binarySort(arr,mid+1,right,findVal); }else if (findVal < midValue){ return binarySort(arr,left,mid-1,findVal); }else { return mid; //找到 } } /**增加条件:查找所有满足条件的元素坐标 * @param arr * @param left * @param right * @param findVal * @return */ public static List<Integer> binarySort2(int[] arr, int left, int right, int findVal){ ArrayList<Integer> indexs = new ArrayList<>(); if(right < left){ //遍历完数组,没有找到符合值 return indexs; } int mid=(left+right)/2; //求出中间值下标 int midValue=arr[mid]; //求出中间值 if (findVal >midValue){ //比中间值大,向右递归 return binarySort2(arr,mid+1,right,findVal); }else if (findVal < midValue){ return binarySort2(arr,left,mid-1,findVal); }else { /*课后思考题: {1,8, 10, 89, 1000, 1000,1234} 当一个有序数组中, * 有多个相同的数值时,如何将所有的数值都查找到,比如这里的 1000 * 思路分析 * 1. 在找到 mid 索引值,不要马上返回 * 2. 向 mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合 ArrayList * 3. 向 mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合 ArrayList * 4. 将 Arraylist 返回*/ //向左查找 int i=mid-1; while (true){ if (i<0 || arr[i] != findVal){ //因为二分查找法是针对有序的数,如果midVal值符合, break; //则其他几个数也在临近的位置(要么就查找完毕要么就没有了退出循环), } //所以后面的阶数条件是arr[i] !=midVal indexs.add(i); i--; } indexs.add(mid); //此时的中间值符合别忘了放入集合中 //向右查找 i=mid+1; while (true){ if (i>arr.length-1 || arr[i] != findVal){ break; } indexs.add(i); i++; } return indexs; //返回集合 } } }
8.3插值查找算法
8.3.1插值查找应用实例
@请对一个有序数组进行插值查找 ,输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"。
//插值查找也是适应有序的数组 public class InsertValueSort { public static void main(String[] args) { int[] arr = new int[100]; for (int i=0;i<100;i++){ arr[i]=i; } int i = insertValueSort(arr, 0, arr.length - 1, 10); System.out.println(i); } /** * @param arr 待查找数组 * @param left 待查找数组的左边边界索引 * @param right 待查找数组的右边边界索引 * @param findNum 待查找的数 * @return */ public static int insertValueSort(int[] arr,int left,int right,int findNum){ //System.out.println("----"); //findNum<arr[0] || findNum>arr[arr.length-1]条件不仅是优化,而且是必要条件 //防止后面的arr[mid]数组越界(倘若findNum非常大) if (left>right || findNum<arr[0] || findNum>arr[arr.length-1]){ return -1; } //插值算法求mid int mid=left+(right-left)*(findNum-arr[left])/(arr[right]-arr[left]); int midValue=arr[mid]; if (findNum<midValue){ //如果小于就往左边循环 return insertValueSort(arr,left,mid-1,findNum); }else if (findNum>midValue){ return insertValueSort(arr,mid+1,right,findNum); }else { return mid; } } }
8.4斐波拉契(黄金分割法)查找算法
8.4.1斐波拉契查找基本介绍
(1)黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是 0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割,也称为中外比。这是一个神奇的数字,会带来意向不大的效果。
(2) 斐波那契数列 {1, 1, 2, 3, 5, 8, 13, 21, 34, 55 } 发现斐波那契数列的两个相邻数 的比例,无限接近 黄金分割值0.618
8.4.2斐波拉契原理
斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid)的位置,mid 不再是中间或插值得到,而是位于黄金分割点附近,即 mid=low+F(k-1)-1(F 代表斐波那契数列),如下图所示
@对F(k-1)-1的理解:
(1)由斐波那契数列 F[k]=F[k-1]+F[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 的两段,即如上图所示。从而中间位置为 mid=low+F(k-1)-1
(2)类似的,每一子段也可以用相同的方式分割
(3)但顺序表长度 n 不一定刚好等于 F[k]-1,所以需要将原来的顺序表长度 n 增加至 F[k]-1。这里的 k 值只要能使得 F[k]-1 恰好大于或等于 n 即可,由以下代码得到,顺序表长度增加后,新增的位置(从 n+1 到 F[k]-1 位置),都赋为 n 位置的值即可。
while(n>fib(k)-1){ k++; }
8.4.3斐波拉契查找应用实例:
@请对一个有序数组进行斐波那契查找 {1,8, 10, 89, 1000, 1234} ,输入一个数看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"。
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("index=" + fibSearch(arr, 189));// 0 } //因为后面我们 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] 值 可能大于 a 的 长度,因此我们需要使用 Arrays 类,构造一个新的数组,并指向 temp[] //不足的部分会使用 0 填充 int[] temp = Arrays.copyOf(a, f[k]); //实际上需求使用 a 数组最后的数填充 temp //举例: //temp = {1,8, 10, 89, 1000, 1234, 0, 0} => {1,8, 10, 89, 1000, 1234, 1234, 1234,} 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] = f[k-2] + f[k-3] //即 在 f[k-1] 的前面继续查找 k-- //即下次循环 mid = f[k-1-1]-1 k--; } else if ( key > temp[mid]) { // 我们应该继续向数组的后面查找(右边) low = mid + 1; //为什么是 k -=2 //说明 //1. 全部元素 = 前面的元素 + 后边元素 //2. f[k] = f[k-1] + f[k-2] //3. 因为后面我们有 f[k-2] 所以可以继续拆分 f[k-1] = f[k-3] + f[k-4] //4. 即在 f[k-2] 的前面进行查找 k -=2 //5. 即下次循环 mid = f[k - 1 - 2] - 1 k -= 2; } else { //找到 //需要确定,返回的是哪个下标 if(mid <= high) { return mid; } else { return high; } } } return -1; } }