Java查找数据结构
1.顺序查找
顺序查找,顾名思义,遍历整个数列,当数列值和该查找值相等就返回该查找所在数列的索引
1.1代码实现
package Search;
/**
* @author zh
* @ClassName : Search.OrderSearch
* @Description :顺序查找
* Created by user on 2021-07-21 16:57:09
* Copyright 2020 user. All rights reserved.
*/
public class OrderSearch {
static int[] arr;
/**
* 顺序查找
* @author zh
* @date 2021/7/21 16:57
* @param arr: 查找元素的数组
* @param val: 查找值
* @return int :返回查找值在数组的索引
**/
public static int orderSearch(int[] arr,int val){
for (int i = 0; i < arr.length; i++) {
if (arr[i] == val){
return i;
}
}
return -1;
}
public static void main(String[] args) {
arr=new int[]{3,2,0,1};
System.out.println(orderSearch(arr, 0));
}
}
2.二分查找
二分查找也称折半查找,原理非常简单,实质就是通过不断改变中间值mid的索引来缩小查找的范围
注:一个数列使用二分查找的前提是该数列是有序的(本文的一切排序都假定数列是从大到小排序的)
2.1二分查找思路及步骤
二分查找实现可以通过递归或非递归(循环)来实现,原理有一样,简单易懂
- 首先定义left左指针和right右指针分别用来指定,查找的数列的索引范围(left和right的初始值分别为0,arr.length-1)
- 通过数组的长度确定中间值mid的索引---->mid=(left+right)/2
- 然后将要查找的值val与arr[mid]进行比较,如果val==arr[mid],那么直接返回该索引;如果val<arr[mid],那么向左开始递归或循环地进行查找;如果val>arr[mid],那么向右开始递归或循环进行查找.
- 递归或循环地结束条件:当在递归中查找到目标值,直接结束,如果最终该值不在该数列中,那么结束的条件为left>right(因为随着递归,left和right之间差值越来越小,直至相等);当在循环时结束条件一样
2.2代码实现
以下二分查找实现了当查找值在数列有多个相同值时,返回该值所有的索引集合
package Search;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author zh
* @ClassName : Search.BinarySearch
* @Description :二分查找/折半查找(使用该查找的前提是该数列是有序的)
* Created by user on 2021-07-21 18:43:52
* Copyright 2020 user. All rights reserved.
*/
public class BinarySearch {
static int[] arr;
用递归实现
/**
* 二分查找(当数列中有多个相同的查找值,可返回所有的索引)
* @author zh
* @date 2021/7/21 18:44
* @param arr: 数组
* @param val: 值
* @param left: 左索引
* @param right: 右索引
* @return List<Integer> :索引值的集合
**/
public static List<Integer> binarySearch(int[] arr, int left, int right, int val){
/*递归*/
if (left>right){
return new ArrayList<Integer>();
}
int mid=(left+right)/2;
if (val==arr[mid]){
ArrayList<Integer> list = new ArrayList<>();
int temp=mid;
list.add(mid);
while (mid-1!=-1&&arr[mid-1] == val){
mid--;
list.add(mid);
}
while (temp+1<arr.length-1&&arr[temp+1]==val){
temp++;
list.add(temp);
}
return list;
}else if(val<arr[mid]){
return binarySearch(arr,left,mid-1,val);
}else {
return binarySearch(arr,mid+1,right,val);
}
}
用循环实现
/**
* 二分查找(当数列中有多个相同的查找值,可返回所有的索引)
* @author zh
* @date 2021/7/21 18:44
* @param arr: 数组
* @param val: 值
* @param left: 左索引
* @param right: 右索引
* @return List<Integer> :索引值的集合
**/
public static List<Integer> binarySearch(int[] arr, int left, int right, int val){
//非递归
int mid;
while (left<=right){
mid = (left+right)/2;
if (val==arr[mid]){
ArrayList<Integer> list = new ArrayList<>();
int temp=mid;
list.add(mid);
while (mid-1!=-1&&arr[mid-1] == val){
mid--;
list.add(mid);
}
while (temp+1<arr.length-1&&arr[temp+1]==val){
temp++;
list.add(temp);
}
return list;
}else if (val<arr[mid]){
right=mid-1;
}else {
left=mid+1;
}
}
return new ArrayList<>();
}
测试主方法
public static void main(String[] args) {
arr=new int[]{1,43,43,43,3242,5351,45111,4541111,5611111,6666666} ;
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
System.out.println(binarySearch(arr, 0, arr.length - 1, 1));
}
3.插值查找
3.1插值查找原理介绍
插值查找算法基于二分查找,和二分查找唯一的不同之处就是mid的索引取值不同,插值查找每次从自适应mid 处开始查找, mid=left+(right-left)*(val-arr[left])/(arr[right]-arr[left])),该种mid的算法是根据数学中的归一法(好像是).
简单补充:归一法是一种简化计算的方式,有两种形式,一种是把数变为(0,1)之间的小数,一种是把有量纲表达式变为无量纲表达式.
当查找的数列的数排列均匀(即每个数之间相差不多)的情况,那么插值查找查找的次数要比二分查找少;反之,次数更多.
插值查找注意事项:
- 对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找, 速度较快.
- 关键字分布不均匀的情况下,该方法不一定比折半查找要好
3.2代码实现
package Search;
/**
* @author zh
* @ClassName : Search.InsertSearch
* @Description :插值查找(基于二分查找,主要为mid和二分查找不同,
* mid=left+(right-left)*(val-arr[left])/(arr[right]-arr[left]))
* Created by user on 2021-07-21 20:14:24
* Copyright 2020 user. All rights reserved.
*/
public class InsertSearch {
static int[] arr;
/**
* 插值查找
* @author zh
* @date 2021/7/21 20:20
* @param arr:
* @param left:
* @param right:
* @param val:
* @return int :
**/
public static int insertSort(int[] arr,int left,int right,int val){
System.out.println("看查找次数");
/*这里的arr[0]>val||arr[arr.length-1]<val一定要判断,因为当val超过最大值或小于最小值,
那么mid就会很大或很小导致数组越界*/
if (left>right||arr[0]>val||arr[arr.length-1]<val){
return -1;
}
int mid=left+(right-left)*(val-arr[left])/(arr[right]-arr[left]);
if (val==arr[mid]){
return mid;
}else if (val < arr[mid]) {
return insertSort(arr,left,mid-1,val);
}else {
return insertSort(arr,mid+1,right,val);
}
}
public static void main(String[] args) {
arr=new int[]{1,43,43,43,3242,5351,45111,4541111,5611111,6666666};
System.out.println(insertSort(arr, 0, arr.length - 1, 45111));
}
}
4.斐波那契查找
4.1斐波那契查找的原理介绍
斐波那契查找也称黄金分割查找,他也是基于二分查找的算法,其核心还是通过改变mid的索引达到缩小查找范围,
按照该算法mid=low+f(k-1)-1
斐波那契查找原理:
首先斐波那契数列有一特性f(k)=f(k-1)+f(k-2),而随着k的增大f(k-1)/f(k)越接近于黄金分割值0.618…,所以该方法也称黄金分割查找;那么根据该算法,把查找数列分成了两个部分,前面部分为f(k-1)长度,后面部分为f(k-2)长度.而mid=low+f(k-1)-1,很容易看出mid的索引指向的值为f(k-1)中的最后一位数.下面是图片
4.1代码实现及思路
package Search;
import java.util.Arrays;
/**
* @author zh
* @ClassName : Search.FibonacciSearch
* @Description :斐波那契查找(基于二分查找,只是mid变了mid=left+f(k-1)-1) (f(k-1)为斐波那契数)
* 所以该查找算法的实质性是通过斐波那契数列找到mid的索引进行再查找
* Created by user on 2021-07-22 09:06:49
* Copyright 2020 user. All rights reserved.
*/
public class FibonacciSearch {
/*该查找算法将数列长度记为f(k),由于f(k)为斐波那契数,所以f(k)=f(k-1)+f(k-2),
所以将数列划分成前面一段为f(k-1),后面一段为f(k-2),mid=left+f(k-1)-1(即mid为f(k-1)中的最后一位数)*/
static int[] arr;
/*该长度是标明测试的数组的最大长度为6765,也就是斐波那契数列的第20位*/
static int maxSize=20;
获得斐波那契数列
/*获得斐波那契数列*/
public static int[] fibonacci(){
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;
}
进行斐波那契查找
/**
* 斐波那契查找
* @author zh
* @date 2021/7/22 9:25
* @param arr:
* @param val :要查找的值
* @return int :返回索引
**/
public static int fibonacciSearch(int[] arr,int val){
/*当数组长度等于1时,由于right=0,得到k的值为0,
所以temp数组长度也为1,导致计算mid时fibonacci(k-1)出现越界*/
if (arr.length==1){
if (arr[0]==val){
return 0;
}else {
return -1;
}
}
int left=0;
int right=arr.length-1;
int mid=0;
/*斐波那契数列的索引*/
int k=0;
int[] fibonacci = fibonacci();
/*根据数组的长度获得斐波那契数列中最接近且刚好大于或等于的数(即length<=fibonacci[k]-->得到k)
* 比如数组长度是7,那么斐波那契数列中最接近的是8,那么k等于5*/
while (right>fibonacci[k]){
k++;
}
/*由于斐波那契数可能不等于数组长度,而是大于数组长度,
所以需要扩容数组(构造一个新的数组,长度为fibonacci[k],并且将arr数组
复制到该数组中并将大于该数组长度的部分的元素值赋值为arr数组中最大的数,
即arr[arr.length-1],以此来保证新数组仍然是个有序数组)*/
int[] temp = Arrays.copyOf(arr, fibonacci[k]);
for (int i = arr.length-1; i < temp.length; i++) {
temp[i]=arr[arr.length-1];
}
while (left<=right){
/*temp[mid]是左半段最后一位,mid的和跟斐波那契数列中的左半段fibonacci[k-1]有关*/
mid=left+fibonacci[k-1]-1;
if (val>temp[mid]){
left=mid+1;
/*首先我们知道k的值是跟为了确定mid的位置,k-2的原因是,当val>mid,那么就去右半段fibonacci[k-2]中找
* fibonacci[k-2]=fibonacci[k-3]+fibonacci[k-4],fibonacci[k-3]为左半段,那么mid肯定
* 跟fibonacci[k-3]有关,而现在为fibonacci[k-1]-->fibonacci[k-1]需要k-2;
* */
k-=2;
}else if(val<temp[mid]){
right=mid-1;
/*和上面的判断方法一样,val<mid就去左半段查找,并寻找左半段fibonacci[k-1]的mid,
fibonacci[k-1]=fibonacci[k-2]+fibonacci[k-3],
中间过程和上面一样,fibonacci[k-1]--->fibonacci[k-2]需要k-1;*/
k--;
}else {
/*由于之前数组已经扩容,所以mid可能会大于原来的数组长度,所以索引可能会不准确
* 但是当mid>arr.length-1时,代表查找的值为数组中最大的,所以直接返回原数组arr中的最后一个元素的索引*/
if (mid<=arr.length-1){
return mid;
}else {
return arr.length-1;
}
}
}
return -1;
}
测试主方法
public static void main(String[] args) {
arr=new int[] {323,324,433,434,33253,432354,54234232};
System.out.println(fibonacciSearch(arr, 323));
}