数据结构(Java)学习笔记——知识点:查找算法
十一、查找算法
1、基本介绍
在Java中,我们常用的查找有四种:
1)顺序(线性查找)
2)二分查找 / 折半查找
3)插值查找
4)斐波那契查找
2、顺序(线性查找)
有一个数列:{1,8,10,89,1000,1234},判断数列中是否包含给定的查找条件的值,如果找到了,提示找到,并且给出下标值。
算法思路
顺序查找就很简单了,只需要用一个for循环遍历数组,然后查找相应数字的下标就行了,没有丝毫的难度。
代码实现
public class SeqSearch {
/*
有一个数列:{1,8,10,89,1000,1234},
判断数列中是否包含给定的查找条件的值,如果找到了,提示找到,并且给出下标值。
*/
public static void main(String[] args) {
int[] arr = {1,8,10,89,1000,1234};
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要查找的值:");
int result = Search(arr,scanner.nextInt());
if( result != -1){
System.out.println("数组中存在该值,下标为:" + result);
}else {
System.out.println("数组中没有该值!");
}
}
/**
* @Author: Cui
* @Description: 顺序查找指定的数字在数组中的下标
* @DateTime: 22:46
* @Params: arr 待查找的数组, num 待查找的数字
* @Return
*/
public static int Search(int[] arr, int num){
for(int index = 0;index < arr.length; index++){
if(num == arr[index]){
return index;
}
}
return -1;
}
}
运行结果
请输入要查找的值:
1000
数组中存在该值,下标为:4
3、二分查找 / 折半查找
有一个数列:{1,8,10,89,1000,1234},判断数列中是否包含给定的查找条件的值,如果找到了,提示找到,并且给出下标值。
算法思路
1)首先要对数组进行排序,然后确定数组的中间下标。两个left和right指针,分别指向数组的前后两端,mid = (left + right) / 2。
2)然后让要查找到的数num和arr[mid]进行比较。
3)如果num > arr[mid] ,就在右边递归进行查找。如果num < arr[mid],就在左边递归进行查找。num = arr[mid] 说明找到了。
4)终止递归的条件是在数组中找到了相应的数字,或者是遍历完整个数组后还是没有找到,又或者是当left > right时就需要退出递归。
代码实现
/*
有一个数列:{1,8,10,89,1000,1234},
判断数列中是否包含给定的查找条件的值,如果找到了,提示找到,并且给出下标值。
*/
public class BinarySearch {
public static void main(String[] args) {
int[] arr = {1, 8, 10, 89, 1000, 1234};
int left = 0;
int right = arr.length - 1;
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要查找的值:");
int result = Search(arr,scanner.nextInt(),left,right);
if( result != -1){
System.out.println("数组中存在该值,下标为:" + result);
}else {
System.out.println("数组中没有该值!");
}
}
/**
* @Author: Cui
* @Description: arr:数组 num:要查询的数组 left:左指针 right:右指针
* @DateTime: 16:21
* @Params: 二分法查找
* @Return 返回-1表示没找到,返回下标表示找到
*/
public static int Search(int[] arr, int num, int left,int right) {
int mid = (left + right) / 2;
if(left > right){
return -1;//如果左边的值大于右边的值,就什么都没找到,返回-1
}
if(num < arr[mid]){
right = mid -1;//在左边查询
}else if(num > arr[mid]){
left = mid + 1;//在右边查询
}else{
return mid;//就是这个值
}
return Search(arr,num,left,right);//递归
}
}
运行结果
请输入要查找的值:
1234
数组中存在该值,下标为:5
如果数列变成了这样:{1,8,10,89,1000,1000,1234},怎么查找才能把所有的东西都找到呢?
只需要在找到1000这个数的时候不要先返回,而是向左和右进行扫描,把数字是1000的下标都方法ArrayList中去。
代码实现(优化)
/*
有一个数列:{1,8,10,89,1000,1234},
判断数列中是否包含给定的查找条件的值,如果找到了,提示找到,并且给出下标值。
*/
public class BinarySearch {
public static void main(String[] args) {
int[] arr = {1, 8, 10, 89, 1000, 1000, 1000, 1000, 1234};
int left = 0;
int right = arr.length - 1;
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要查找的值:");
ArrayList<Integer> arrayList = Search(arr,scanner.nextInt(),left,right);
if(arrayList != null){
System.out.println("数组中存在该值,下标为:" + arrayList);
}else {
System.out.println("数组中没有该值!");
}
}
/**
* @Author: Cui
* @Description: arr:数组 num:要查询的数组 left:左指针 right:右指针
* @DateTime: 16:21
* @Params: 二分法查找
* @Return 返回-1表示没找到,返回下标表示找到
*/
public static ArrayList<Integer> Search(int[] arr, int num, int left,int right) {
int mid = (left + right) / 2;
if(left > right){
return new ArrayList<>();//如果左边的值大于右边的值,就什么都没找到,返回-1
}
if(num < arr[mid]){
right = mid -1;//在左边查询
}else if(num > arr[mid]){
left = mid + 1;//在右边查询
}else{
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(mid);//先把当前值保存了
int midLeft = mid - 1;
int midRight = mid + 1;
//在这里对左右进行扫描,把符合条件的值都找到
//向左查找
while(true){
if(midLeft < 0 || arr[midLeft] != num){//退出
break;
}
list.add(midLeft);
midLeft -= 1;//下标左移
}
//向右查找
while(true){
if(midRight > arr.length - 1 || arr[midRight] != num){//退出
break;
}
list.add(midRight);
midRight += 1;//下标右移
}
return list;
}
return Search(arr,num,left,right);//递归
}
}
运行结果(优化)
请输入要查找的值:
1234
数组中存在该值,下标为:[8]
4、插值查找
插值查找类似于二分查找,不同的是插值查找每次都是自适应的mid处开始查找。
插值查找的操作很神奇,尽量带入数学的思想进行理解。
算法思路
将二分查找的公式改变一下:
其实就是变成,int mid = low + (high - low) * (key - arr[low]) / (arr[high] - arr[low])
我们把这个等式移项,会得到:(mid - low) / (high - low) = (key - arr[low]) / (arr[high] - arr[low])
这不就是数学中,两个相似三角形的斜边与直角边的比相等么。
根据这个求出来的 mid 值,是会根据所要查找的值进行不断优化的。
这种算法在顺序,归率的线性数组种,要比二分查找要快捷、准确的多。
代码实现
public class InsertValueSearch {
public static void main(String[] args) {
int[] arr = new int[100];
for(int i = 0; i < arr.length; i++){
arr[i] = i + 1;
}
int index = insertValueSearch(arr, 0, arr.length - 1, 78);
System.out.println("下标为:" + index);
}
/**
* @Author: Cui
* @Description: arr:数组 num:要查询的数组 left:左指针 right:右指针
* @DateTime: 16:40
* @Params: 插值查找算法
* @Return 如果找到了,返回对应的下标,如果没找到,返回-1
*/
public static int insertValueSearch(int[] arr, int left, int right, int nums){
if(left > right || nums < arr[0] || nums > arr[arr.length - 1]){
return -1;
}
//求出mid
int mid = left + (right + left) * (nums - arr[left]) / (arr[right] - arr[left]);
int midValue = arr[mid];
if(nums > midValue){
//应该向右递归
return insertValueSearch(arr, mid + 1, right, nums);
} else if (nums < midValue) {
//应该向左递归
return insertValueSearch(arr, mid - 1, right, nums);
}else{
return mid;
}
}
}
运行结果
下标为:77
插值查找注意事项:
1)对于数据量较大,数据分布较为平均的查找来说,用插值查找,速度更快。
2)对于数据分布不平均的情况,该方法不一定比二分查找好。
5、斐波那契查找(黄金分割法)
黄金分割点是指把一条线段分割为两部分,使其中一部分与全部的比等于另一部分与这部分的比。去前三位数字的近似值就是 0.618。
斐波那契数列{1,1,2,3,5,8,13,21,34,55}。发现斐波那契数列的两个相邻数的比例,无限接近黄金分割值 0.618。
算法思路
斐波那契查找的原理和前两种相似,仅仅是改变了中间结点(mid)的位置。mid 不再是通过中间值或插值得到,而是在黄金分割点附近,即:mid = low + F(k - 1)- 1。(其中F代表斐波那契数列),如图所示:
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)顺序表长度不一定刚好等于 F[K] - 1,所以需要将原来的顺序表长度 n 增加到 F[K] - 1。这里的 k 值只要使得 F[K] - 1 恰好大于或等于 n 即可,由以下代码得到,顺序表长度增加以后,新增的位置按照顺序赋值即可。
while(n > fib(k) - 1){
k++;
}
代码实现
public class FibonacciSearch {
public static int maxSize = 20;
public static void main(String[] args) {
int[] arr = {1,8,10,89,1000,1234};
int index = fibSearch(arr, 1000);
System.out.println("下标为:" + index);
}
//因为后面我们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;
}
/**
* @Author: Cui
* @Description: 用斐波那契查找
* @DateTime: 13:05
* @Params: a 待查找数组,num 要查找的数字
* @Return 找到返回对应下标,找不到返回-1
*/
//编写斐波那契查找算法
public static int fibSearch(int[] a, int num){
int mid;
int low = 0;
int high = a.length - 1;
int k = 0;//表示斐波那契分割数值的下标
int f[] = fib();//获取到斐波那契数列
//得到斐波那契分割数值的下标
while(high > f[k] - 1){
k++;
}
//f[k]的值可能大于a的长度,因此需要用Arrays类,构造一个新的数组,把长度进行填充
int[] temp = Arrays.copyOf(a,f[k]);
for(int i = high + 1; i < temp.length; i++){
temp[i] = a[high];
}
//用while来循环处理,找到我们的数num
while(low <= high){
mid = low + f[k - 1] - 1;
if(num < temp[mid]){//小了,向左边查找
high = mid - 1;
k--;
}else if (num > temp[mid]){//大了,向右边查找
low = mid + 1;
k -= 2;//
}else{
//找到了
if(mid <= high){
return mid;
}else{
return high;
}
}
}
return -1;//没找到,返回-1
}
}
运行结果
下标为:4